Smooth Transitions and Animations in Vue 3
Master Vue 3's transition system. Learn to create CSS and JavaScript animations with Transition, TransitionGroup, and custom hooks.
Written by Francisco Zapata
Animations bring user interfaces to life. Vue 3 offers a built-in transition system that makes it easy to animate elements as they enter, leave, or change in the DOM. With the <Transition> and <TransitionGroup> components, you can create polished visual experiences without relying on external libraries.
The Transition Component
The <Transition> component applies enter and leave animations to a single element or component. Vue automatically handles adding and removing CSS classes at the right time.
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<template>
<button @click="show = !show">Toggle</button>
<Transition name="fade">
<p v-if="show">Hello, world!</p>
</Transition>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
Transition Classes
Vue applies 6 classes during the transition lifecycle:
Enter:v-enter-from: Starting state before enteringv-enter-active: Active state during enter (define your transition here)v-enter-to: Ending state of enter
v-leave-from: Starting state before leavingv-leave-active: Active state during leavev-leave-to: Ending state of leave
When using name="fade", classes are prefixed: fade-enter-from, fade-enter-active, etc.
Advanced CSS Transitions
You can combine transform, opacity, and other properties for more elaborate effects:
<Transition name="slide-fade">
<div v-if="show" class="box">Content</div>
</Transition>
<style>
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>
CSS Keyframe Animations
For more complex animations, use @keyframes:
<Transition name="bounce">
<p v-if="show">Bounce!</p>
</Transition>
<style>
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% { transform: scale(0); }
50% { transform: scale(1.25); }
100% { transform: scale(1); }
}
</style>
Transition Modes
When toggling between two elements, both transitions happen simultaneously by default. Use mode to control the order:
<Transition name="fade" mode="out-in">
<component :is="currentComponent" :key="currentTab" />
</Transition>
out-in: Current element leaves first, then new one enters (most common)in-out: New element enters first, then current one leaves
JavaScript Hooks
For programmatic animations or integration with libraries like GSAP, use JavaScript hooks:
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
:css="false"
>
<div v-if="show">Animated with JS</div>
</Transition>
<script setup>
function onEnter(el, done) {
gsap.to(el, {
opacity: 1,
y: 0,
duration: 0.5,
onComplete: done
})
}
function onLeave(el, done) {
gsap.to(el, {
opacity: 0,
y: 20,
duration: 0.3,
onComplete: done
})
}
</script>
The :css="false" prop tells Vue not to apply CSS classes, giving full control to JavaScript. It is important to call done() to signal that the animation has finished.
TransitionGroup: Animating Lists
<TransitionGroup> is for animating multiple elements, such as dynamic lists:
<script setup>
import { ref } from 'vue'
const items = ref([1, 2, 3, 4, 5])
let id = items.value.length
function addItem() {
const index = Math.floor(Math.random() * items.value.length)
items.value.splice(index, 0, ++id)
}
function removeItem(item) {
items.value = items.value.filter(i => i !== item)
}
</script>
<template>
<button @click="addItem">Add</button>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item" @click="removeItem(item)">
{{ item }}
</li>
</TransitionGroup>
</template>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* Important: animate the movement of remaining elements */
.list-move {
transition: transform 0.5s ease;
}
.list-leave-active {
position: absolute;
}
</style>
Differences from Transition
- Renders an actual container element (defined with
tag) - Does not support
mode(out-in / in-out) - Child elements MUST have a unique
:key - Classes are applied to each individual child
- Supports the
v-moveclass for animating repositioning
Route Transitions with Vue Router
You can animate page transitions by combining <Transition> with <RouterView>:
<RouterView v-slot="{ Component, route }">
<Transition :name="route.meta.transition || 'fade'" mode="out-in">
<component :is="Component" :key="route.path" />
</Transition>
</RouterView>
This allows defining transitions per route using meta fields:
const routes = [
{
path: '/home',
component: Home,
meta: { transition: 'slide-left' }
},
{
path: '/about',
component: About,
meta: { transition: 'slide-right' }
}
]
Appear Transitions
To animate elements when the page first loads:
<Transition appear name="fade">
<h1>Welcome</h1>
</Transition>
Performance Tips
1. Use will-change
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
will-change: opacity;
}
2. Prefer transform and opacity
These properties are the most efficient because they do not cause layout reflow:
/* Efficient */
.slide-enter-from { transform: translateX(100px); opacity: 0; }
/* Less efficient - causes reflow */
.slide-enter-from { margin-left: 100px; opacity: 0; }
3. Avoid animating too many elements
With <TransitionGroup> and large lists, consider pagination or virtualization to avoid animating hundreds of elements simultaneously.
4. Use v-show instead of v-if for frequent toggles
v-show keeps the element in the DOM and only toggles its visibility, which is more efficient for frequent toggles.
Conclusion
Vue 3's transition system is versatile and accessible. From simple fades to complex list animations, you have the tools you need without additional dependencies. Start with simple CSS transitions and evolve to JavaScript hooks when you need more control. The key is finding the balance between visual fluidity and performance.
Comments (0)
Leave a comment
Be the first to comment