javascripthtmlcssvue.jsvue-transitions

Transition multiple elements at the same time on Vue component render


Simple case shown in the following jsfiddle:

https://jsfiddle.net/hsak2rdu/

I want to swap and animate two elements, but fail. As you can see after clicking the toggle button in the playground, the second element flashes into the final position.

I want them both animated at the same time, like crossing each other. Is it possible?

Template:

<div id="app">
  <div class="dom" v-for="(_d, _i) in list" :key="_d.id" :style="{ top: _i * 50 + 'px'}">
    <span>{{_d.text}}{{_i}}</span>
  </div>
  <button @click="handle">Toggle</button>
  {{list}}
</div>

JS:

new Vue({
  el: '#app',
  data: {
    show: true,
    list: [
      {
        id:1,
        text:'First'
      },
      {
        id:2,
        text:'Second'
      }
    ]
  },
  methods:{
    handle: function (){
      console.log("DEBUG", this.list)
      let a = JSON.parse(JSON.stringify(this.list[0]));
      let b = JSON.parse(JSON.stringify(this.list[1]))
      this.$set(this.list, 0, b);
      this.$set(this.list, 1, a);
    }
  }
});

Solution

  • The only necessary change is to wrap the v-for in a <transition-group>:

    <transition-group tag="div" name="list">
      <div class="dom" v-for="(_d, _i) in list" :key="_d.id" :style="{ top: _i * 20 + 'px' }">
        <span>{{_d.text}}{{_i}}</span>
      </div>
    </transition-group>
    

    From the docs:

    This might seem like magic, but under the hood, Vue is using an animation technique called FLIP to smoothly transition elements from their old position to their new position using transforms

    Here's a demo:

    new Vue({
      el: '#app',
      data: () => ({
        show: true,
        list: [
          {
            id:1,
            text:'First'
          },
          {
            id:2,
            text:'Second'
          }
        ]
      }),
      methods:{
        handle: function (){
          console.log("DEBUG", this.list)
          let a = JSON.parse(JSON.stringify(this.list[0]));
          let b = JSON.parse(JSON.stringify(this.list[1]))
          this.$set(this.list, 0, b);
          this.$set(this.list, 1, a);
        }
      }
    });
    .dom{
      position: absolute;
      transition: all 1s linear;
      opacity: 1;
    }
    button{
      margin-top: 50px;
    }
    #app{
      margin-top: 50px;
      position: relative;
    }
    <div id="app">
      <transition-group tag="div" name="list">
        <div class="dom" v-for="(_d, _i) in list" :key="_d.id" :style="{ top: _i * 20 + 'px' }">
          <span>{{_d.text}}{{_i}}</span>
        </div>
      </transition-group>
      <button @click="handle">Toggle</button>
      {{list}}
    </div>
    
    <script src="https://unpkg.com/vue/dist/vue.js"></script>