Vue Animations with v-for

The built-in <TransitionGroup> component in Vue helps us to animate elements that are added to our page with v-for.

The <TransitionGroup> Component

The <TransitionGroup> component is used around elements created with v-for, to give these elements individual animations when they are added or removed.

Tags created with v-for inside the <TransitionGroup> component must be defined with the key attribute.

The <TransitionGroup> component is only rendered as an HTML tag if we define it to be a specific tag by using the tag prop, like this:

<TransitionGroup tag="ol">
  <li v-for="x in products" :key="x">
    {{ x }}
  </li>
</TransitionGroup>

This is the result from the code above, after it is rendered by Vue:

<ol>
  <li>Apple</li>
  <li>Pizza</li>
  <li>Rice</li>
</ol>

We can now add CSS code to animate new items when they are added to the list:

<style>
  .v-enter-from {
    opacity: 0;
    rotate: 180deg;
  }
  .v-enter-to {
    opacity: 1;
    rotate: 0deg;
  }
  .v-enter-active {
    transition: all 0.7s;
  }
</style>

In this example, new items will be animated simply by adding them to 'products' array:

Example

App.vue:

<template>
  <h3>The <TransitionGroup> Component</h3>
  <p>New products are given animations using the <TransitionGroup> component.</p>
  <input type="text" v-model="inpName"> 
  <button @click="addEl">Add</button>
  <TransitionGroup tag="ol">
    <li v-for="x in products" :key="x">
      {{ x }}
    </li>
  </TransitionGroup>
</template>

<script>
  export default {
    data() {
      return {
        products: ['Apple','Pizza','Rice'],
        inpName: ''
      }
    },
    methods: {
      addEl() {
        const el = this.inpName;
        this.products.push(el);
        this.inpName = null;
      }
    }
  }
</script>

<style>
  .v-enter-from {
    opacity: 0;
    rotate: 180deg;
  }
  .v-enter-to {
    opacity: 1;
    rotate: 0deg;
  }
  .v-enter-active {
    transition: all 0.7s;
  }
</style>
Run Example »

Add and Remove Elements

When removing elements in between other elements, the other elements will fall into place where removed element was. To animate how the rest of the list items fall into place when an element is removed, we will use the automatically generated v-move class.

But first, let's look at an example where it is not animated how other items fall into place when an element is removed:

Example

App.vue:

<template>
  <h3>The <TransitionGroup> Component</h3>
  <p>New products are given animations using the <TransitionGroup> component.</p>
  <button @click="addDie">Roll</button>
  <button @click="removeDie">Remove random</button><br>
  <TransitionGroup>
    <div v-for="x in dice" :key="x" class="diceDiv" :style="{ backgroundColor: 'hsl('+x*40+',85%,85%)' }">
      {{ x }}
    </div>
  </TransitionGroup>
</template>

<script>
  export default {
    data() {
      return {
        dice: [],
        inpName: ''
      }
    },
    methods: {
      addDie() {
        const newDie = Math.ceil(Math.random()*6);
        this.dice.push(newDie);
      },
      removeDie() {
        if(this.dice.length>0){
          this.dice.splice(Math.floor(Math.random()*this.dice.length), 1);
        }
      }
    },
    mounted() {
      this.addDie();
      this.addDie();
      this.addDie();
    }
  }
</script>

<style>
.v-enter-from {
  opacity: 0;
  translate: 200px 0;
  rotate: 360deg;
}
.v-enter-to {
  opacity: 1;
  translate: 0 0;
  rotate: 0deg;
}
.v-enter-active,
.v-leave-active {
  transition: all 0.7s;
}
.v-leave-from { opacity: 1; }
.v-leave-to { opacity: 0; }
.diceDiv {
  margin: 10px;
  width: 30px;
  height: 30px;
  line-height: 30px;
  vertical-align: middle;
  text-align: center;
  border: solid black 1px;
  border-radius: 5px;
  display: inline-block;
}
</style>
Run Example »

As you can see in the example above, when an item is removed, the items after the removed item suddenly jumps into their new positions. To animate the rest of the items when an item is removed, we use the automatically generated v-move class.

The v-move class animates the other elements as the removed item leaves, but there is one problem: The removed item still exists and takes up place until it is removed, and so the v-move class will not have any effect. To give the v-move class something to animate we can set position: absolute; to the v-leave-active class. When position: absolute; is set during the removal period, the removed item is still visible, but does not take up place.

In this example, the only difference from the previous example are the two new CSS classes added on line 14 and 17:

Example

App.vue:

<style>
.v-enter-from {
  opacity: 0;
  translate: 200px 0;
  rotate: 360deg;
}
.v-enter-to {
  opacity: 1;
  translate: 0 0;
  rotate: 0deg;
}
.v-enter-active,
.v-leave-active,
.v-move {
  transition: all 0.7s;
}
.v-leave-active { position: absolute; }
.v-leave-from { opacity: 1; }
.v-leave-to { opacity: 0; }
.diceDiv {
  margin: 10px;
  width: 30px;
  height: 30px;
  line-height: 30px;
  vertical-align: middle;
  text-align: center;
  border: solid black 1px;
  border-radius: 5px;
  display: inline-block;
}
</style>
Run Example »

A Larger Example

Let's use the example above as the basis for a new example.

In this example it becomes even more clear how the whole list is animated when a new item is added or removed, or when the whole array is sorted.

In this example we can:

  • Remove items by clicking on them
  • Sort the items
  • Add new items at a random place in the list

Example

App.vue:

<template>
  <h3>The <TransitionGroup> Component</h3>
  <p>Items inside the <TransitionGroup> component are animated when they are created or removed.</p>
  <button @click="addDie">Roll</button>
  <button @click="addDie10">Roll 10 dice</button>
  <button @click="dice.sort(compareFunc)">Sort</button>
  <button @click="dice.sort(shuffleFunc)">Shuffle</button><br>
  <TransitionGroup>
    <div 
    v-for="x in dice" 
    :key="x.keyNmbr" 
    class="diceDiv" 
    :style="{ backgroundColor: 'hsl('+x.dieNmbr*60+',85%,85%)' }"
    @click="removeDie(x.keyNmbr)">
      {{ x.dieNmbr }}
    </div>
  </TransitionGroup>
</template>

<script>
  export default {
    data() {
      return {
        dice: [],
        keyNumber: 0
      }
    },
    methods: {
      addDie() {
        const newDie = {
          dieNmbr: Math.ceil(Math.random()*6),
          keyNmbr: this.keyNumber
        };
        this.dice.splice(Math.floor(Math.random()*this.dice.length),0,newDie);
        this.keyNumber++;
      },
      addDie10() {
        for(let i=0; i<10; i++) {
          this.addDie();
        }
      },
      compareFunc(a,b){
        return a.dieNmbr - b.dieNmbr;
      },
      shuffleFunc(a,b){
        return Math.random()-0.5;
      },
      removeDie(key) {
        const pos = this.dice.map(e => e.keyNmbr).indexOf(key);
        this.dice.splice(pos, 1);
      }
    },
    mounted() {
      this.addDie10();
    }
  }
</script>

<style>
.v-enter-from {
  opacity: 0;
  scale: 0;
  rotate: 360deg;
}
.v-enter-to {
  opacity: 1;
  scale: 1;
  rotate: 0deg;
}
.v-enter-active,
.v-leave-active,
.v-move {
  transition: all 0.7s;
}
.v-leave-active { position: absolute; }
.v-leave-from { opacity: 1; }
.v-leave-to { opacity: 0; }
.diceDiv {
  margin: 10px;
  width: 30px;
  height: 30px;
  line-height: 30px;
  vertical-align: middle;
  text-align: center;
  border: solid black 1px;
  border-radius: 5px;
  display: inline-block;
}
.diceDiv:hover {
  cursor: pointer;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
#app {
  position: relative;
}
</style>
Run Example »

Vue Exercises

Test Yourself With Exercises

Exercise:

What is the name of the Vue specific component used to animate elements created or removed with v-for?

< tag="ol">
  <li v-for="x in products" :key="x">
    {{ x }}
  </li>
</>

Start the Exercise



Copyright 1999-2023 by Refsnes Data. All Rights Reserved.