Table of Contents
- Understanding Vue.js Transitions: Core Concepts
- Getting Started: Setting Up Your Vue Project
- Basic Transition Components:
<transition>and<transition-group> - Transitioning Single Elements/Components
- List Transitions with
<transition-group> - State-Driven Animations
- Advanced Techniques: Staggered Animations & Custom Hooks
- Integrating with CSS Libraries (Tailwind, Animate.css)
- Best Practices for Performance & Usability
- Conclusion
- References
Understanding Vue.js Transitions: Core Concepts
Before diving into code, let’s clarify how Vue handles animations. Vue’s transition system hooks into the enter/leave lifecycle of elements (e.g., when they’re inserted, removed, or toggled). It dynamically applies CSS classes or runs JavaScript functions during these phases to create smooth transitions.
Key Terminology:
- Enter Phase: When an element is inserted into the DOM (e.g., via
v-iforv-show). - Leave Phase: When an element is removed from the DOM.
- Transition Classes: Vue adds/removes 6 CSS classes during enter/leave phases to control animations:
v-enter-from(start state for enter)v-enter-active(active state for enter, applies during transition)v-enter-to(end state for enter)v-leave-from(start state for leave)v-leave-active(active state for leave)v-leave-to(end state for leave)
- JS Hooks: Functions like
before-enter,enter, orleavethat let you control animations with JavaScript (e.g., integrating GSAP).
Getting Started: Setting Up Your Vue Project
To follow along, you’ll need a basic Vue project. We’ll use Vite (Vue’s recommended build tool) for speed:
-
Create a new Vue project:
npm create vite@latest my-vue-animations -- --template vue cd my-vue-animations npm install npm run dev -
Open
src/App.vue—this is where we’ll write our examples.
Basic Transition Components: <transition> and <transition-group>
Vue provides two primary components for animations:
1. <transition>: For Single Elements/Components
Use <transition> to animate single elements (e.g., a modal, a toggleable div) or dynamic components (via <component :is="...">). It wraps the element and applies transition classes automatically.
Props:
name: Customizes the prefix for transition classes (e.g.,name="fade"usesfade-enter-frominstead ofv-enter-from).appear: Triggers the enter transition when the element first renders (default:false).mode: Controls timing for overlapping enter/leave transitions (in-outorout-in; default: simultaneous).
2. <transition-group>: For Lists
Use <transition-group> to animate lists (e.g., adding/removing items, reordering). Unlike <transition>, it renders a real DOM element (default: <span>) and requires key attributes for list items.
Props:
tag: Specifies the root element (e.g.,tag="ul"for a list).move-class: Defines the class for animating item reordering (e.g.,move-class="fade-move").
Transitioning Single Elements/Components
Let’s start with a simple example: a button that toggles a div with a fade transition.
Step 1: Basic Fade Transition
Update src/App.vue with this code:
<template>
<div class="app">
<button @click="show = !show">Toggle Content</button>
<!-- Wrap the element to animate with <transition> -->
<transition name="fade" appear>
<div v-if="show" class="box">Hello, Vue Transitions!</div>
</transition>
</div>
</template>
<script setup>
import { ref } from 'vue';
const show = ref(false); // Reactive state to toggle visibility
</script>
<style scoped>
.box {
padding: 20px;
margin: 10px 0;
background: #42b983;
color: white;
border-radius: 4px;
}
/* Transition classes (prefix: fade-) */
.fade-enter-from, .fade-leave-to {
opacity: 0; /* Start/end state: invisible */
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s ease; /* Animate opacity over 0.5s */
}
</style>
How It Works:
-
When
showistrue, the div is inserted. Vue adds:fade-enter-from(opacity: 0)fade-enter-active(triggers the transition)- After a frame,
fade-enter-fromis removed, andfade-enter-to(implicitlyopacity: 1) is added. - When the transition ends,
fade-enter-activeis removed.
-
When
showisfalse, the reverse happens withfade-leave-from(opacity: 1) →fade-leave-active→fade-leave-to(opacity: 0).
List Transitions with <transition-group>
Lists require <transition-group> because <transition> only handles single elements. Let’s animate a list that adds/removes items with a slide-and-fade effect.
Step 1: Slide + Fade List Transition
<template>
<div class="app">
<input
v-model="newItem"
@keyup.enter="addItem"
placeholder="Type and press Enter to add"
/>
<button @click="removeItem">Remove Last Item</button>
<!-- Animate the list with <transition-group> -->
<transition-group
name="slide-fade"
tag="ul"
class="list"
>
<li
v-for="(item, index) in items"
:key="item.id"
@click="removeItem(index)"
>
{{ item.text }}
</li>
</transition-group>
</div>
</template>
<script setup>
import { ref } from 'vue';
const newItem = ref('');
const items = ref([
{ id: 1, text: 'First item' },
{ id: 2, text: 'Second item' }
]);
// Add item to list
const addItem = () => {
if (newItem.value.trim()) {
items.value.push({
id: Date.now(), // Unique ID
text: newItem.value
});
newItem.value = '';
}
};
// Remove item by index
const removeItem = (index) => {
items.value.splice(index, 1);
};
</script>
<style scoped>
.list {
list-style: none;
padding: 0;
}
.list li {
padding: 10px;
margin: 5px 0;
background: #f0f0f0;
border-radius: 4px;
cursor: pointer;
}
/* Enter/leave transitions */
.slide-fade-enter-from,
.slide-fade-leave-to {
opacity: 0;
transform: translateX(20px); /* Slide in from right */
}
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 0.3s ease; /* Animate opacity + transform */
}
/* Move transition for reordering */
.slide-fade-move {
transition: transform 0.3s ease;
}
</style>
Key Notes:
keyis mandatory: Each list item needs a uniquekey(Vue uses this to track DOM changes).slide-fade-move: This class animates item positions when the list reorders (e.g., if you sort the list later).- Implicit vs. Explicit Transitions: The
transformproperty ensures smooth movement without layout shifts (better performance than animatingmarginortop).
State-Driven Animations
Not all animations depend on DOM insertion/removal. You can animate state changes (e.g., a slider, a counter, or a color picker) using Vue’s reactivity system.
Example: Animate a Number Counter
Let’s animate a counter that increments from 0 to a target value using requestAnimationFrame.
<template>
<div class="app">
<input
type="number"
v-model="target"
min="0"
max="100"
class="input"
/>
<p class="counter">{{ displayValue.toFixed(0) }}</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const target = ref(0);
const displayValue = ref(0);
// Watch for target changes and animate
watch(target, (newVal) => {
const start = displayValue.value;
const end = Number(newVal);
const duration = 500; // Animation duration (ms)
const startTime = performance.now();
const animate = (currentTime) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1); // 0 → 1
displayValue.value = start + (end - start) * progress;
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
});
</script>
<style scoped>
.input {
padding: 8px;
margin-right: 10px;
}
.counter {
font-size: 2rem;
color: #42b983;
}
</style>
Integrating with JS Libraries (e.g., GSAP)
For complex animations (e.g., springs, path animations), use libraries like GSAP (GreenSock Animation Platform). Vue’s JS hooks (e.g., enter, leave) work seamlessly with GSAP.
Example: Animate with GSAP
First, install GSAP:
npm install gsap
Then animate a div’s scale on enter/leave:
<template>
<transition
name="bounce"
@enter="onEnter"
@leave="onLeave"
>
<div v-if="show" class="box"></div>
</transition>
</template>
<script setup>
import { ref } from 'vue';
import { gsap } from 'gsap';
const show = ref(false);
const onEnter = (el) => {
// Animate scale from 0 to 1
gsap.from(el, { scale: 0, duration: 0.5, ease: 'bounce.out' });
};
const onLeave = (el, done) => {
// Animate scale from 1 to 0, then call done()
gsap.to(el, { scale: , duration: 0.3, onComplete: done });
};
</script>
<style scoped>
.box {
width: 100px;
height: 100px;
background: #42b983;
border-radius: 4px;
}
</style>
Why done()? For JS-driven leave transitions, call done() to notify Vue when the animation finishes (prevents premature DOM removal).
Advanced Techniques: Staggered Animations & Custom Hooks
Staggered Animations for Lists
Add delays to list items so they animate one after another (e.g., a cascade effect). Use the item’s index to calculate delays.
<template>
<transition-group
name="stagger"
tag="div"
@before-enter="beforeEnter"
@enter="enter"
>
<div
v-for="(item, index) in items"
:key="item"
:data-index="index"
class="stagger-item"
>
{{ item }}
</div>
</transition-group>
</template>
<script setup>
import { ref } from 'vue';
import { gsap } from 'gsap';
const items = ref(['Item 1', 'Item 2', 'Item 3', 'Item 4']);
const beforeEnter = (el) => {
el.style.opacity = 0;
el.style.transform = 'translateY(20px)';
};
const enter = (el, done) => {
const index = el.dataset.index;
gsap.to(el, {
opacity: 1,
transform: 'translateY(0)',
duration: 0.5,
delay: index * 0.1, // Stagger by 0.1s per item
onComplete: done
});
};
</script>
Custom Transition Classes
Override default transition classes with props like enter-from-class, enter-active-class, etc. Useful for integrating with CSS libraries like Animate.css.
<transition
enter-active-class="animate__animated animate__bounceIn"
leave-active-class="animate__animated animate__bounceOut"
>
<div v-if="show">Animate with Animate.css!</div>
</transition>
Integrating with CSS Libraries
Tailwind CSS
Tailwind’s utility classes work seamlessly with Vue transitions. Use transition, duration, and ease classes to define animations.
Example: Slide Transition with Tailwind
<template>
<transition name="slide">
<div v-if="show" class="p-4 bg-blue-500 text-white">
Tailwind + Vue Transition
</div>
</transition>
</template>
<style>
/* Use Tailwind utilities in transition classes */
.slide-enter-from, .slide-leave-to {
@apply translate-x-full opacity-0;
}
.slide-enter-active, .slide-leave-active {
@apply transition-all duration-300 ease-in-out;
}
</style>
Best Practices for Performance & Usability
- Animate Cheap Properties: Use
transformandopacityfor animations—they don’t trigger layout reflows (unlikewidthormargin). - Avoid Overanimation: Subtlety improves UX (e.g., a 200ms fade instead of a 1s bounce).
- Use
will-changeSparingly: Hint to browsers about upcoming animations (e.g.,will-change: transform) but avoid overusing it (can cause memory issues). - Test on Mobile: Mobile devices have limited resources—keep animations lightweight.
Conclusion
Vue.js makes animations and transitions accessible to developers of all skill levels. Whether you’re animating single elements with <transition>, dynamic lists with <transition-group>, or state changes with watchers, Vue’s tools simplify the process while offering flexibility for advanced use cases.
By combining Vue’s reactivity with CSS transitions or JS libraries like GSAP, you can create polished, performant animations that delight users. Start small (e.g., a fade transition) and gradually experiment with staggered lists or state-driven effects—your UI will thank you!