VueJS - Transition & Animation
VueJS - Transition and Animation
Section titled “VueJS - Transition and Animation”In this chapter, we will explore how to apply engaging transitions and animations to elements in your VueJS applications when they are added, updated, or removed from the DOM.
Using the Component
Section titled “Using the Component”Vue provides a built-in <transition> wrapper component. You can wrap this component around any element or component that enters or leaves the DOM (typically using directives like v-if, v-show, or for dynamic components).
Syntax
Section titled “Syntax”<transition name="my-transition"> <p v-if="show">Hello</p></transition>When an element wrapped in <transition> is inserted or removed, Vue automatically adds/removes CSS classes at different stages of the transition. The name attribute (e.g., my-transition) serves as a prefix for these classes. If no name is provided, the prefix defaults to v-.
Transition Classes
Section titled “Transition Classes”Six classes are applied for enter/leave transitions:
- Enter Phase:
v-enter-from: Starting state for enter. Added before the element is inserted, removed one frame after insertion.v-enter-active: Active state for enter. Applied during the entire entering phase. Used to define duration, delay, and easing curve. Removed when the transition finishes.v-enter-to: Ending state for enter. Added one frame after insertion (at the same timev-enter-fromis removed), removed when the transition finishes.- Leave Phase:
v-leave-from: Starting state for leave. Added immediately when a leaving transition is triggered, removed after one frame.v-leave-active: Active state for leave. Applied during the entire leaving phase. Used to define duration, delay, and easing curve. Removed when the transition finishes.v-leave-to: Ending state for leave. Added one frame after the leaving transition is triggered (at the same timev-leave-fromis removed), removed when the transition finishes.
If you used a name attribute like name="fade", the classes would be .fade-enter-from, .fade-enter-active, etc.
Example: Fade Transition
Section titled “Example: Fade Transition”<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Vue 3 Transition Example</title> <script src="https://unpkg.com/vue@3"></script> <style> /* Base styles for the element */ .demo-p { font-size: 20px; color: darkcyan; }
/* Define transition states */ .fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
/* Optional: Define enter-to and leave-from if needed, often opacity: 1 is the default */ .fade-enter-to, .fade-leave-from { opacity: 1; } </style></head><body> <div id="app"> <button @click="show = !show">Toggle Text</button> <transition name="fade"> <p class="demo-p" v-if="show">Animation Example</p> </transition> </div>
<script> const { createApp, ref } = Vue;
createApp({ setup() { const show = ref(true); return { show }; } }).mount('#app'); </script></body></html>In this example:
- We use Vue 3’s Composition API (
createApp,ref). - The
<p>tag is conditionally rendered usingv-if="show". - It’s wrapped in
<transition name="fade">. - The CSS classes
.fade-enter-activeand.fade-leave-activedefine the transition duration and easing. .fade-enter-fromand.fade-leave-todefine the state where the element is invisible (opacity 0).
When the ‘Toggle Text’ button is clicked, the show ref changes. If show becomes true, the paragraph fades in over 0.5 seconds. If show becomes false, it fades out.
CSS Animations
Section titled “CSS Animations”You can also use CSS animations instead of transitions. The usage with the <transition> component is similar, but Vue waits for the animationend event instead of transitionend. You typically define the animation in the *-enter-active or *-leave-active classes.
Example: Bounce Animation
Section titled “Example: Bounce Animation”<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Vue 3 Animation Example</title> <script src="https://unpkg.com/vue@3"></script> <style> .bounce-enter-active { animation: bounce-in 0.5s; } .bounce-leave-active { animation: bounce-in 0.5s reverse; /* Use reverse animation for leave */ }
@keyframes bounce-in { 0% { transform: scale(0); } 50% { transform: scale(1.25); } 100% { transform: scale(1); } }
.demo-div { width: 100px; height: 100px; background-color: tomato; border-radius: 50%; margin-top: 20px; } </style></head><body> <div id="app"> <button @click="show = !show">Toggle Ball</button> <transition name="bounce"> <div class="demo-div" v-if="show"></div> </transition> </div>
<script> const { createApp, ref } = Vue;
createApp({ setup() { const show = ref(true); return { show }; } }).mount('#app'); </script></body></html>Here, we define a @keyframes animation named bounce-in. We apply this animation to the .bounce-enter-active class. For the leaving animation (.bounce-leave-active), we apply the same animation but in reverse direction. When the button is toggled, the red circle appears with a bouncing effect or disappears with a reverse bounce.
Using Animation Libraries (e.g., Animate.css)
Section titled “Using Animation Libraries (e.g., Animate.css)”You can easily integrate third-party CSS animation libraries like Animate.css. Instead of defining the transition classes yourself, you can provide custom class names directly to the <transition> component using specific attributes.
enter-from-classenter-active-classenter-to-classleave-from-classleave-active-classleave-to-class
These attributes have higher priority than the regular class names and are especially useful for combining Vue’s transitions with libraries like Animate.css.
Example with Animate.css
Section titled “Example with Animate.css”<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Vue 3 Animate.css Example</title> <!-- Use a modern version of Animate.css --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> <script src="https://unpkg.com/vue@3"></script> <style> .demo-p { font-size: 24px; font-weight: bold; color: steelblue; } </style></head><body> <div id="app"> <button @click="show = !show">Toggle with Animate.css</button>
<transition enter-active-class="animate__animated animate__fadeInDown" leave-active-class="animate__animated animate__fadeOutUp" > <p class="demo-p" v-if="show">Using Animate.css!</p> </transition> </div>
<script> const { createApp, ref } = Vue;
createApp({ setup() { const show = ref(true); return { show }; } }).mount('#app'); </script></body></html>In this code, we include the Animate.css library. We then use the enter-active-class and leave-active-class attributes on the <transition> component. We provide the necessary Animate.css classes (animate__animated is required, plus the specific animation like animate__fadeInDown). Now, toggling the paragraph uses the predefined animations from the library.
Explicit Transition Duration
Section titled “Explicit Transition Duration”Vue automatically detects when the transition or animation has ended by listening for transitionend or animationend events. However, if you have complex transitions (e.g., a CSS transition mixed with a CSS animation whose duration differs), or if Vue fails to detect the duration correctly, you can specify an explicit duration using the duration prop.
<!-- Same duration for enter and leave --><transition :duration="1000">...</transition>
<!-- Different durations for enter and leave --><transition :duration="{ enter: 500, leave: 800 }">...</transition>The duration is specified in milliseconds.
JavaScript Hooks
Section titled “JavaScript Hooks”For more complex animations or scenarios where CSS transitions/animations are not sufficient (e.g., animating element properties that CSS can’t handle directly, coordinating multiple elements), you can use JavaScript hooks. You define methods and bind them to <transition> component events.
@before-enter(el)@enter(el, done): Important: Calldone()to signal the end of the enter animation.@after-enter(el)@enter-cancelled(el)@before-leave(el)@leave(el, done): Important: Calldone()to signal the end of the leave animation.@after-leave(el)@leave-cancelled(el)(only available withv-show)
When using only JavaScript hooks (no CSS transitions/animations), it’s good practice to add :css="false" to the <transition> component. This tells Vue to skip the CSS detection, potentially improving performance slightly and preventing CSS rules from interfering.
Example: JS Hook with Simple Opacity Change
Section titled “Example: JS Hook with Simple Opacity Change”<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Vue 3 JS Hooks Example</title> <script src="https://unpkg.com/vue@3"></script></head><body> <div id="app"> <button @click="show = !show">Toggle with JS</button> <transition @before-enter="onBeforeEnter" @enter="onEnter" @leave="onLeave" :css="false" /* Tell Vue we are handling animations via JS */ > <p style="font-size: 20px; color: green;" v-if="show"> Animating with JavaScript! </p> </transition> </div>
<script> const { createApp, ref } = Vue;
createApp({ setup() { const show = ref(false);
const onBeforeEnter = (el) => { // Set initial state before enter el.style.opacity = 0; el.style.transform = 'translateY(20px)'; };
const onEnter = (el, done) => { // Start the animation // Using simple requestAnimationFrame for demo let start; const duration = 500; // ms
function step(timestamp) { if (start === undefined) start = timestamp; const elapsed = timestamp - start; const progress = Math.min(elapsed / duration, 1);
el.style.opacity = progress; el.style.transform = `translateY(${20 * (1 - progress)}px)`;
if (progress < 1) { requestAnimationFrame(step); } else { done(); // Signal animation completion } } requestAnimationFrame(step); };
const onLeave = (el, done) => { // Start the leave animation (similar logic, reversing opacity/transform) let start; const duration = 300; // ms
function step(timestamp) { if (start === undefined) start = timestamp; const elapsed = timestamp - start; const progress = Math.min(elapsed / duration, 1);
el.style.opacity = 1 - progress; el.style.transform = `translateY(${20 * progress}px)`;
if (progress < 1) { requestAnimationFrame(step); } else { done(); // Signal animation completion } } requestAnimationFrame(step); };
return { show, onBeforeEnter, onEnter, onLeave }; } }).mount('#app'); </script></body></html>This example uses requestAnimationFrame for a basic JavaScript animation controlling opacity and position. Libraries like GSAP (GreenSock Animation Platform) are often used for more complex JS animations, providing powerful tweening capabilities and timeline control. Remember to call the done callback in @enter and @leave when the animation is complete.
Common Pitfall: Forgetting to call the done callback in @enter and @leave hooks will cause the transition to hang indefinitely.
Transition on Initial Render
Section titled “Transition on Initial Render”To apply a transition when the component first mounts (initial render), add the appear attribute to the <transition> component.
<transition name="fade" appear> <p v-if="true">Fades in on load</p></transition>You can also specify custom CSS classes or JavaScript hooks specifically for the appear transition using attributes like appear-active-class, appear-from-class, appear-to-class, or JS hooks like @before-appear, @appear, @after-appear, @appear-cancelled.
Transitioning Between Elements/Components
Section titled “Transitioning Between Elements/Components”The <transition> component can also handle transitions when switching between elements using v-if/v-else-if/v-else, or when switching dynamic components via <component :is="...">. However, a common requirement is that both elements must have a unique key attribute for Vue to correctly track and transition them.
<transition name="fade" mode="out-in"> <button v-if="isEditing" key="edit">Edit</button> <button v-else key="save">Save</button></transition>The mode attribute controls the timing: out-in (current element transitions out first, then new element transitions in) or in-out (new element transitions in first, then current element transitions out). out-in is generally recommended for smoother visual results.
List Transitions with
Section titled “List Transitions with ”For animating lists rendered with v-for, Vue provides the <TransitionGroup> component. It renders a real DOM element (defaulting to <span>, configurable with the tag prop) and applies transitions to the items inside.
Key differences from <transition>:
- It renders an actual element (e.g.,
<ul>,<div>) instead of being abstract. - Transition classes are applied to the inner elements, not the group element itself.
- Each item in the list MUST have a unique
keyattribute. - It supports move transitions (
v-moveclass) for when the list order changes, which requires using CSStransform.
Example: List Fade
Section titled “Example: List Fade”<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Vue 3 TransitionGroup Example</title> <script src="https://unpkg.com/vue@3"></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); } /* Ensure leaving items are taken out of layout flow so that moving animations can be calculated correctly. */ .list-leave-active { position: absolute; } </style></head><body> <div id="app"> <button @click="addItem">Add Item</button> <button @click="removeItem">Remove Item</button> <transition-group name="list" tag="ul"> <li v-for="item in items" :key="item.id" class="list-item"> {{ item.text }} </li> </transition-group> </div>
<script> const { createApp, ref } = Vue;
createApp({ setup() { const items = ref([ { id: 1, text: 'Item 1' }, { id: 2, text: 'Item 2' }, { id: 3, text: 'Item 3' } ]); let nextId = 4;
const addItem = () => { const randomIndex = Math.floor(Math.random() * items.value.length); items.value.splice(randomIndex, 0, { id: nextId++, text: `Item ${nextId -1}` }); };
const removeItem = () => { if (items.value.length > 0) { const randomIndex = Math.floor(Math.random() * items.value.length); items.value.splice(randomIndex, 1); } };
return { items, addItem, removeItem }; } }).mount('#app'); </script></body></html>This example shows adding and removing items from a list with a fade and slide transition. Note the use of key on each li and the tag="ul" prop on <transition-group>. The .list-leave-active { position: absolute; } style is important for smooth move animations when items are removed.
Further Reading: Vue’s official documentation provides in-depth details on transitions: https://vuejs.org/guide/built-ins/transition.html and list transitions: https://vuejs.org/guide/built-ins/transition-group.html