Vue Animations with v-for

Animate lists and multiple elements

🎪 What are List Animations?

Vue list animations use TransitionGroup to animate multiple elements rendered with v-for. Each item gets smooth enter, leave, and move transitions, creating engaging list interactions like adding, removing, or reordering items.


<!-- Animate list items -->
<TransitionGroup name="list" tag="ul">
  <li v-for="item in items" :key="item">{{ item }}</li>
</TransitionGroup>
                                    

List Animation Features

📝

TransitionGroup

Animate multiple elements

<TransitionGroup name="list">
  <div v-for="item in items">
</TransitionGroup>
🔑

Key Attribute

Required for list animations

<div v-for="item in items" 
     :key="item.id">
🎯

Move Class

Smooth position changes

.list-move {
  transition: transform 0.5s;
}
🏷️

Tag Prop

Specify wrapper element

<TransitionGroup tag="ul">
  <li>...</li>
</TransitionGroup>

🔹 Basic List Animation

Animate adding and removing list items:

<div id="app">
  <button @click="addItem">Add Item</button>
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item" @click="removeItem(item)">
      {{ item }}
    </li>
  </TransitionGroup>
</div>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

ul {
  list-style: none;
  padding: 0;
}

li {
  padding: 10px;
  background: #42b983;
  color: white;
  margin: 5px 0;
  border-radius: 4px;
  cursor: pointer;
}
</style>

<script>
createApp({
  data() {
    return {
      items: ['Item 1', 'Item 2', 'Item 3'],
      nextNum: 4
    }
  },
  methods: {
    addItem() {
      this.items.push(`Item ${this.nextNum++}`);
    },
    removeItem(item) {
      const index = this.items.indexOf(item);
      this.items.splice(index, 1);
    }
  }
}).mount('#app');
</script>

Output:

  • Item 1
  • Item 2
  • Item 3

🔹 List with Move Transition

Smooth animations when items change position:

<div id="app">
  <button @click="shuffle">Shuffle</button>
  <TransitionGroup name="list" tag="div" class="container">
    <div v-for="item in items" :key="item" class="item">
      {{ item }}
    </div>
  </TransitionGroup>
</div>

<style>
.list-move,
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}

.list-leave-active {
  position: absolute;
}

.container {
  position: relative;
}

.item {
  padding: 15px;
  background: #42b983;
  color: white;
  margin: 5px;
  border-radius: 4px;
  display: inline-block;
}
</style>

<script>
createApp({
  data() {
    return {
      items: [1, 2, 3, 4, 5]
    }
  },
  methods: {
    shuffle() {
      this.items = this.items.sort(() => Math.random() - 0.5);
    }
  }
}).mount('#app');
</script>

Output:

1
2
3
4
5

🔹 Staggered List Animation

Delay animations for each item:

<div id="app">
  <button @click="show = !show">Toggle List</button>
  <TransitionGroup
    name="stagger"
    tag="ul"
    @before-enter="onBeforeEnter"
    @enter="onEnter"
  >
    <li v-for="(item, index) in items" 
        :key="item"
        :data-index="index"
        v-show="show">
      {{ item }}
    </li>
  </TransitionGroup>
</div>

<style>
ul {
  list-style: none;
  padding: 0;
}

li {
  padding: 10px;
  background: #42b983;
  color: white;
  margin: 5px 0;
  border-radius: 4px;
}
</style>

<script>
createApp({
  data() {
    return {
      items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'],
      show: true
    }
  },
  methods: {
    onBeforeEnter(el) {
      el.style.opacity = 0;
      el.style.transform = 'translateX(-30px)';
    },
    onEnter(el, done) {
      const delay = el.dataset.index * 100;
      setTimeout(() => {
        el.style.transition = 'all 0.5s ease';
        el.style.opacity = 1;
        el.style.transform = 'translateX(0)';
        done();
      }, delay);
    }
  }
}).mount('#app');
</script>

Output:

  • Apple
  • Banana
  • Cherry
  • Date
  • Elderberry

🔹 Todo List with Animations

Practical example with add/remove animations:

<div id="app">
  <input v-model="newTodo" @keyup.enter="addTodo" placeholder="Add todo">
  <button @click="addTodo">Add</button>
  
  <TransitionGroup name="todo" tag="ul">
    <li v-for="todo in todos" :key="todo.id" class="todo-item">
      {{ todo.text }}
      <button @click="removeTodo(todo.id)">×</button>
    </li>
  </TransitionGroup>
</div>

<style>
.todo-enter-active,
.todo-leave-active {
  transition: all 0.3s ease;
}

.todo-enter-from {
  opacity: 0;
  transform: translateY(-20px);
}

.todo-leave-to {
  opacity: 0;
  transform: translateX(100px);
}

.todo-move {
  transition: transform 0.3s ease;
}

ul {
  list-style: none;
  padding: 0;
}

.todo-item {
  padding: 10px;
  background: white;
  border: 1px solid #ddd;
  margin: 5px 0;
  border-radius: 4px;
  display: flex;
  justify-content: space-between;
}

.todo-item button {
  background: #ff4444;
  color: white;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
}
</style>

<script>
createApp({
  data() {
    return {
      newTodo: '',
      todos: [],
      nextId: 1
    }
  },
  methods: {
    addTodo() {
      if (this.newTodo.trim()) {
        this.todos.push({
          id: this.nextId++,
          text: this.newTodo
        });
        this.newTodo = '';
      }
    },
    removeTodo(id) {
      this.todos = this.todos.filter(todo => todo.id !== id);
    }
  }
}).mount('#app');
</script>

Output:

  • Sample Todo

🔹 Important Tips

  • Always use :key: Required for Vue to track each element
  • Use unique keys: Keys must be unique for each item
  • Add move class: For smooth position transitions
  • Position absolute for leaving: Prevents layout jumps
  • Tag prop: Specify the wrapper element type

🧠 Test Your Knowledge

Which component animates multiple elements with v-for?