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