Vue.js Transition Groups
When working with animations in Vue.js, you'll eventually need to animate lists of items as they're added, removed, or reordered. Vue's <transition-group>
component is specifically designed for this purpose, providing a powerful way to create fluid list animations with minimal effort.
Introduction to Transition Groups
Unlike the standard <transition>
component which can only animate a single element entering and leaving the DOM, <transition-group>
allows you to animate multiple elements within a list.
Key features of <transition-group>
:
- Renders as a real DOM element (default is
<span>
, configurable viatag
attribute) - Every child element must have a unique
key
attribute - CSS transitions and animations work the same way as with
<transition>
- Supports the FLIP animation technique for smooth movement transitions
- Provides an additional
v-move
class for configuring how elements animate when changing position
Basic Usage
Let's start with a simple example of animating a list of items:
<template>
<div>
<button @click="add">Add Item</button>
<button @click="remove">Remove Item</button>
<transition-group name="list" tag="ul">
<li v-for="item in items" :key="item" class="list-item">
{{ item }}
</li>
</transition-group>
</div>
</template>
<script>
export default {
data() {
return {
items: [1, 2, 3, 4, 5],
nextNum: 6
}
},
methods: {
add() {
this.items.splice(Math.floor(Math.random() * this.items.length), 0, this.nextNum++)
},
remove() {
if (this.items.length) {
this.items.splice(Math.floor(Math.random() * this.items.length), 1)
}
}
}
}
</script>
<style>
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-move {
transition: transform 0.5s;
}
</style>
In this example:
- We have a list of numbers displayed as list items
- Two buttons that add or remove items randomly
- The
list-enter-active/list-leave-active
classes control transitions for entering and leaving - The
list-move
class smooths out the position changes when items are reordered
Understanding the v-move Class
The v-move
class (or {name}-move
if you've specified a name) is what makes <transition-group>
special. When items change position, Vue applies this class to elements that are moving to their new position.
This animation is based on the FLIP technique (First, Last, Invert, Play), which calculates the element's start and end positions and then creates an efficient animation between them.
Here's how it works behind the scenes:
- First: Vue records the current position of all elements
- Last: After DOM updates, it records the final positions
- Invert: It applies transforms to place elements at their original positions
- Play: It transitions those transforms to zero, creating smooth movement
Staggering List Transitions
For a more sophisticated effect, we can create staggered animations where each item animates with a slight delay:
<template>
<div>
<button @click="shuffle">Shuffle</button>
<transition-group name="staggered-list" tag="ul" :css="false"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave">
<li v-for="(item, index) in items" :key="item"
:data-index="index"
class="staggered-list-item">
{{ item }}
</li>
</transition-group>
</div>
</template>
<script>
import gsap from 'gsap'
export default {
data() {
return {
items: [1, 2, 3, 4, 5]
}
},
methods: {
shuffle() {
this.items = this.items.sort(() => Math.random() - 0.5)
},
beforeEnter(el) {
el.style.opacity = 0
el.style.transform = 'translateY(30px)'
},
enter(el, done) {
const delay = el.dataset.index * 150
setTimeout(() => {
gsap.to(el, {
opacity: 1,
y: 0,
duration: 0.6,
onComplete: done
})
}, delay)
},
leave(el, done) {
const delay = el.dataset.index * 50
setTimeout(() => {
gsap.to(el, {
opacity: 0,
y: 30,
duration: 0.6,
onComplete: done
})
}, delay)
}
}
}
</script>
<style>
.staggered-list-item {
display: inline-block;
margin-right: 10px;
padding: 10px 20px;
background: #f3f3f3;
border-radius: 4px;
}
</style>
This example uses JavaScript animations with GSAP (GreenSock Animation Platform) and hook functions to create a staggered animation effect based on the element's index in the list.
Real-World Example: Animated Shopping Cart
Here's a practical example of a shopping cart that animates items as they're added or removed:
<template>
<div class="shopping-cart">
<h2>Shopping Cart</h2>
<div class="products">
<div v-for="product in availableProducts" :key="product.id" class="product">
<h3>{{ product.name }} - ${{ product.price }}</h3>
<button @click="addToCart(product)">Add to Cart</button>
</div>
</div>
<div class="cart">
<h3>Your Cart</h3>
<p v-if="!cartItems.length">Your cart is empty</p>
<transition-group name="cart-items" tag="ul" class="cart-list">
<li v-for="(item, index) in cartItems" :key="item.id" class="cart-item">
<span class="item-name">{{ item.name }}</span>
<span class="item-price">${{ item.price }}</span>
<button @click="removeFromCart(index)" class="remove-button">×</button>
</li>
</transition-group>
<div class="cart-total">
<h4>Total: ${{ cartTotal }}</h4>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
availableProducts: [
{ id: 1, name: "Product 1", price: 19.99 },
{ id: 2, name: "Product 2", price: 29.99 },
{ id: 3, name: "Product 3", price: 39.99 }
],
cartItems: []
}
},
computed: {
cartTotal() {
return this.cartItems.reduce((total, item) => total + item.price, 0).toFixed(2)
}
},
methods: {
addToCart(product) {
// Create a new object to ensure unique reference (for animation)
const cartItem = { ...product, id: Date.now() }
this.cartItems.push(cartItem)
},
removeFromCart(index) {
this.cartItems.splice(index, 1)
}
}
}
</script>
<style>
.cart-list {
list-style-type: none;
padding: 0;
}
.cart-item {
display: flex;
justify-content: space-between;
padding: 10px;
margin-bottom: 5px;
background-color: #f8f8f8;
border-radius: 4px;
}
.remove-button {
background: #ff5252;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
}
.cart-items-enter-active,
.cart-items-leave-active {
transition: all 0.5s ease;
}
.cart-items-enter-from {
opacity: 0;
transform: translateX(30px);
}
.cart-items-leave-to {
opacity: 0;
transform: translateX(-30px);
}
.cart-items-move {
transition: transform 0.5s ease;
}
</style>
This shopping cart example demonstrates:
- Adding and removing items with animations
- Using a different enter/leave animation direction
- Maintaining smooth transitions when items shift position
- A practical real-world application for list transitions
Advanced Techniques: List Reordering
A common use case for transition groups is animating list reordering. This example shows how to create a sortable list with smooth animations:
<template>
<div>
<button @click="sortByName">Sort by Name</button>
<button @click="sortByPrice">Sort by Price</button>
<transition-group name="list" tag="ul" class="product-list">
<li v-for="product in products" :key="product.id" class="product-item">
<div class="product-name">{{ product.name }}</div>
<div class="product-price">${{ product.price.toFixed(2) }}</div>
</li>
</transition-group>
</div>
</template>
<script>
export default {
data() {
return {
products: [
{ id: 1, name: "Apple", price: 1.99 },
{ id: 2, name: "Banana", price: 0.99 },
{ id: 3, name: "Cherry", price: 2.99 },
{ id: 4, name: "Grape", price: 3.99 },
{ id: 5, name: "Orange", price: 1.49 }
]
}
},
methods: {
sortByName() {
this.products = [...this.products].sort((a, b) => a.name.localeCompare(b.name))
},
sortByPrice() {
this.products = [...this.products].sort((a, b) => a.price - b.price)
}
}
}
</script>
<style>
.product-list {
list-style-type: none;
padding: 0;
}
.product-item {
display: flex;
justify-content: space-between;
padding: 15px;
margin-bottom: 10px;
background-color: #f5f5f5;
border-radius: 4px;
width: 300px;
}
.list-move {
transition: transform 0.5s ease;
}
/* Optional: Fade effect when items get repositioned */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
This example demonstrates:
- Sorting a list by different criteria
- Animating items as they move to their new positions
- Creating a fluid user experience during data updates
Important Considerations
When working with transition groups, keep these points in mind:
-
Always use unique keys: Each item in a transition group must have a unique key for animations to work correctly.
-
Default rendering element:
<transition-group>
renders as a<span>
by default. Use thetag
prop to change this. -
CSS classes: The same six transition classes apply as with regular transitions (
v-enter-from
,v-enter-active
, etc.). -
The v-move class: This class is applied to elements when they change position, allowing for smooth movement animations.
-
Performance: For large lists, be cautious with complex animations as they can impact performance. Test on your target devices.
Summary
Vue.js transition groups provide a powerful way to animate lists and collections of elements with minimal code:
- They use the
<transition-group>
component to render a real DOM element containing multiple items - Each child must have a unique key for proper animation tracking
- The FLIP animation technique powers smooth movement transitions
- You can create sophisticated effects like staggered animations, sorting, and filtering with transition groups
- They're ideal for enhancing user experiences in lists, grids, shopping carts, and more
With transition groups, you can create engaging, dynamic interfaces that respond to data changes with smooth, professional animations.
Additional Resources and Exercises
Resources
- Vue.js Official Documentation on Transition Groups
- FLIP Animation Technique
- GreenSock Animation Platform (GSAP)
Exercises
-
Basic List Transition Create a simple todo list with the ability to add and remove items with animations.
-
Filtered List Build a list with filter buttons that show/hide items based on categories, with smooth transitions for appearing and disappearing items.
-
Kanban Board Column Implement a single column of a kanban board where tasks can be added, removed, and reordered with appropriate animations.
-
Advanced: Animated Data Dashboard Create a data dashboard with charts or statistics that animate when data is updated or when switching between different data views.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)