Vue Animations
The built-in<Transition> component in Vue helps us to do animations when elements are added or removed withv-if,v-show or with dynamic components.
There is nothing wrong with using plain CSS transitions and animations in other cases.
A Short Introduction to CSS Transition and Animation
This part of the tutorial requires knowledge about basic CSSanimations andtransitions.
But before we use the Vue specific built-in<Transition> component to create animations, let's look at two examples of how plain CSS animations and transitions can be used with Vue.
Example
App.vue:
<template> <h1>Basic CSS Transition</h1> <button @click="this.doesRotate = true">Rotate</button> <div :class="{ rotate: doesRotate }"></div></template><script>export default { data() { return { doesRotate: false } }}</script><style scoped> .rotate { rotate: 160deg; transition: rotate 1s; } div { border: solid black 2px; background-color: lightcoral; width: 60px; height: 60px; } h1, button, div { margin: 10px; }</style>Run Example »In the example above, we usev-bind to give the<div> tag a class so that it rotates. The reason that the rotation takes 1 second, is that it is defined with the CSStransition property.
In the example below, we see how we can move an object with the CSSanimation property.
Example
App.vue:
<template> <h1>Basic CSS Animation</h1> <button @click="this.doesMove = true">Start</button> <div :class="{ move: doesMove }"></div></template><script>export default { data() { return { doesMove: false } }}</script><style scoped> .move { animation: move .5s alternate 4 ease-in-out; } @keyframes move { from { translate: 0 0; } to { translate: 70px 0; } } div { border: solid black 2px; background-color: lightcoral; border-radius: 50%; width: 60px; height: 60px; } h1, button, div { margin: 10px; }</style>Run Example »The <Transition> Component
There is nothing wrong with using plain CSS transitions and animations like we did in the two examples above.
But luckily Vue provides us with the built-in<Transition> component in cases where we want to animate an element as it is removed from, or added to, our application withv-if orv-show, because that would be hard to do with plain CSS animation.
Let's first make an application where a button adds or removes a<p> tag:
Example
App.vue:
<template> <h1>Add/Remove <p> Tag</h1> <button @click="this.exists = !this.exists">{{btnText}}</button><br> <p v-if="exists">Hello World!</p></template><script>export default { data() { return { exists: false } }, computed: { btnText() { if(this.exists) { return 'Remove'; } else { return 'Add'; } } }}</script><style> p { background-color: lightgreen; display: inline-block; padding: 10px; }</style>Run Example »Now let's wrap the<Transition> component around the<p> tag, and see how we can animate the removal of the<p> tag.
When we use the<Transition> component, we automatically get six different CSS classes we can use to animate when elements are added or removed.
In the example below we will use the automatically available classesv-leave-from andv-leave-to to make a fade out animation when the<p> tag is removed:
Example
App.vue:
<template> <h1>Add/Remove <p> Tag</h1> <button @click="this.exists = !this.exists">{{btnText}}</button><br> <Transition> <p v-if="exists">Hello World!</p> </Transition></template><script>export default { data() { return { exists: false } }, computed: { btnText() { if(this.exists) { return 'Remove'; } else { return 'Add'; } } }}</script><style> .v-leave-from { opacity: 1; } .v-leave-to { opacity: 0; } p { background-color: lightgreen; display: inline-block; padding: 10px; transition: opacity 0.5s; }</style>Run Example »The Six <Transition> Classes
There are six classes automatically available to us when we use the<Transition> component.
As an element inside the<Transition> component isadded, we can use these first three classes to animate that transition:
- v-enter-from
- v-enter-active
- v-enter-to
And as an element isremoved inside the<Transition> component, we can use the next three classes :
- v-leave-from
- v-leave-active
- v-leave-to
Note:There can only be one element on root level of the<Transition> component.
Now, let's use four of these classes so that we can animate both when the<p> tag is added, and when it is removed.
Example
App.vue:
<template> <h1>Add/Remove <p> Tag</h1> <button @click="this.exists = !this.exists">{{btnText}}</button><br> <Transition> <p v-if="exists">Hello World!</p> </Transition></template><script>export default { data() { return { exists: false } }, computed: { btnText() { if(this.exists) { return 'Remove'; } else { return 'Add'; } } }}</script><style> .v-enter-from { opacity: 0; translate: -100px 0; } .v-enter-to { opacity: 1; translate: 0 0; } .v-leave-from { opacity: 1; translate: 0 0; } .v-leave-to { opacity: 0; translate: 100px 0; } p { background-color: lightgreen; display: inline-block; padding: 10px; transition: all 0.5s; }</style>Run Example »We can also use thev-enter-active andv-leave-active to set styling or animationduring adding orduring removal of an element:
Example
App.vue:
<template> <h1>Add/Remove <p> Tag</h1> <button @click="this.exists = !this.exists">{{btnText}}</button><br> <Transition> <p v-if="exists">Hello World!</p> </Transition></template><script>export default { data() { return { exists: false } }, computed: { btnText() { if(this.exists) { return 'Remove'; } else { return 'Add'; } } }}</script><style> .v-enter-active { background-color: lightgreen; animation: added 1s; } .v-leave-active { background-color: lightcoral; animation: added 1s reverse; } @keyframes added { from { opacity: 0; translate: -100px 0; } to { opacity: 1; translate: 0 0; } } p { display: inline-block; padding: 10px; border: dashed black 1px; }</style>Run Example »The Transition 'name' Prop
In case you have several<Transition> components, but you want at least one of the<Transition> components to have a different animation, you need different names for the<Transition> components to tell them apart.
We can choose the name of a<Transition> component with thename prop, and that changes the name of the transition classes as well, so that we can set different CSS animation rules for that component.
<Transition name="swirl">If the transitionname prop value is set to'swirl', the automatically available classes will now start with'swirl-' instead of'v-':
- swirl-enter-from
- swirl-enter-active
- swirl-enter-to
- swirl-leave-from
- swirl-leave-active
- swirl-leave-to
In the example below we use thename prop to give the<Transition> components different animations. One<Transition> component is not given a name, and is therefore given animations using the automatically generated CSS classes starting with 'v-'. The other<Transition> component is given a name 'swirl' so that it can be given rules for animation with the automatically generated CSS classes starting with 'swirl-'.
Example
App.vue:
<template> <h1>Add/Remove <p> Tag</h1> <p>The second transition in this example has the name prop "swirl", so that we can keep the transitions apart with different class names.</p> <hr> <button @click="this.p1Exists = !this.p1Exists">{{btn1Text}}</button><br> <Transition> <p v-if="p1Exists" id="p1">Hello World!</p> </Transition> <hr> <button @click="this.p2Exists = !this.p2Exists">{{btn2Text}}</button><br> <Transition name="swirl"> <p v-if="p2Exists" id="p2">Hello World!</p> </Transition></template><script>export default { data() { return { p1Exists: false, p2Exists: false } }, computed: { btn1Text() { if(this.p1Exists) { return 'Remove'; } else { return 'Add'; } }, btn2Text() { if(this.p2Exists) { return 'Remove'; } else { return 'Add'; } } }}</script><style> .v-enter-active { background-color: lightgreen; animation: added 1s; } .v-leave-active { background-color: lightcoral; animation: added 1s reverse; } @keyframes added { from { opacity: 0; translate: -100px 0; } to { opacity: 1; translate: 0 0; } } .swirl-enter-active { animation: swirlAdded 1s; } .swirl-leave-active { animation: swirlAdded 1s reverse; } @keyframes swirlAdded { from { opacity: 0; rotate: 0; scale: 0.1; } to { opacity: 1; rotate: 360deg; scale: 1; } } #p1, #p2 { display: inline-block; padding: 10px; border: dashed black 1px; } #p2 { background-color: lightcoral; }</style>Run Example »JavaScript Transition Hooks
Every Transition class as just mentioned corresponds to an event that we can hook into to run some JavaScript code.
| Transition Class | JavaScript Event |
|---|---|
| v-enter-from | before-enter |
| v-enter-active | enter |
| v-enter-to | after-enterenter-cancelled |
| v-leave-from | before-leave |
| v-leave-active | leave |
| v-leave-to | after-leaveleave-cancelled (v-show only) |
When theafter-enter event happens in the example below, a method runs that displays a red<div> element.
Example
App.vue:
<template> <h1>JavaScript Transition Hooks</h1> <p>This code hooks into "after-enter" so that after the initial animation is done, a method runs that displays a red div.</p> <button @click="pVisible=true">Create p-tag!</button><br> <Transition @after-enter="onAfterEnter"> <p v-show="pVisible" id="p1">Hello World!</p> </Transition> <br> <div v-show="divVisible">This appears after the "enter-active" phase of the transition.</div></template><script>export default { data() { return { pVisible: false, divVisible: false } }, methods: { onAfterEnter() { this.divVisible = true; } }}</script><style scoped> .v-enter-active { animation: swirlAdded 1s; } @keyframes swirlAdded { from { opacity: 0; rotate: 0; scale: 0.1; } to { opacity: 1; rotate: 360deg; scale: 1; } } #p1, div { display: inline-block; padding: 10px; border: dashed black 1px; } #p1 { background-color: lightgreen; } div { background-color: lightcoral; }</style>Run Example »You can use the "Toggle" button in the example below to interrupt the enter transition phase of the<p> element so that theenter-cancelled event is triggered:
Example
App.vue:
<template> <h1>The 'enter-cancelled' Event</h1> <p>Click the toggle button again before the enter animation is finished to trigger the 'enter-cancelled' event.</p> <button @click="pVisible=!pVisible">Toggle</button><br> <Transition @enter-cancelled="onEnterCancelled"> <p v-if="pVisible" id="p1">Hello World!</p> </Transition> <br> <div v-if="divVisible">You interrupted the "enter-active" transition.</div></template><script>export default { data() { return { pVisible: false, divVisible: false } }, methods: { onEnterCancelled() { this.divVisible = true; } }}</script><style scoped> .v-enter-active { animation: swirlAdded 2s; } @keyframes swirlAdded { from { opacity: 0; rotate: 0; scale: 0.1; } to { opacity: 1; rotate: 720deg; scale: 1; } } #p1, div { display: inline-block; padding: 10px; border: dashed black 1px; } #p1 { background-color: lightgreen; } div { background-color: lightcoral; }</style>Run Example »The 'appear' Prop
If we have an element that we want to animate when the page loads, we need to use theappear prop on the<Transition> component.
<Transition appear> ...</Transition>In this example, theappear prop starts an animation when the page load for the first time:
Example
App.vue:
<template> <h1>The 'appear' Prop</h1> <p>The 'appear' prop starts the animation when the p tag below is rendered for the first time as the page opens. Without the 'appear' prop, this example would have had no animation.</p> <Transition appear> <p id="p1">Hello World!</p> </Transition></template><style> .v-enter-active { animation: swirlAdded 1s; } @keyframes swirlAdded { from { opacity: 0; rotate: 0; scale: 0.1; } to { opacity: 1; rotate: 360deg; scale: 1; } } #p1 { display: inline-block; padding: 10px; border: dashed black 1px; background-color: lightgreen; }</style>Run Example »Transition Between Elements
The<Transition> component can also be used to switch between several elements, as long as we make sure that only one element is shown at a time with the use of<v-if> and<v-else-if>:
Example
App.vue:
<template> <h1>Transition Between Elements</h1> <p>Click the button to get a new image.</p> <p>The new image is added before the previous is removed. We will fix this in the next example with mode="out-in".</p> <button @click="newImg">Next image</button><br> <Transition> <img src="/img_pizza.svg" v-if="imgActive === 'pizza'"> <img src="/img_apple.svg" v-else-if="imgActive === 'apple'"> <img src="/img_cake.svg" v-else-if="imgActive === 'cake'"> <img src="/img_fish.svg" v-else-if="imgActive === 'fish'"> <img src="/img_rice.svg" v-else-if="imgActive === 'rice'"> </Transition></template><script>export default { data() { return { imgActive: 'pizza', imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'], indexNbr: 0 } }, methods: { newImg() { this.indexNbr++; if(this.indexNbr >= this.imgs.length) { this.indexNbr = 0; } this.imgActive = this.imgs[this.indexNbr]; } }}</script><style> .v-enter-active { animation: swirlAdded 1s; } .v-leave-active { animation: swirlAdded 1s reverse; } @keyframes swirlAdded { from { opacity: 0; rotate: 0; scale: 0.1; } to { opacity: 1; rotate: 360deg; scale: 1; } } img { width: 100px; margin: 20px; } img:hover { cursor: pointer; }</style>Run Example »mode="out-in"
In the example above, the next image is added before the previous image is removed.
We use themode="out-in" prop and prop value on the<Transition> component so that the removal of an element is finished before the next element is added.
Example
In addition tomode="out-in", this example also uses a computed value 'imgActive' instead of the 'newImg' method we used in the previous example.
App.vue:
<template> <h1>mode="out-in"</h1> <p>Click the button to get a new image.</p> <p>With mode="out-in", the next image is not added until the current image is removed. Another difference from the previous example, is that here we use computed prop instead of a method.</p> <button @click="indexNbr++">Next image</button><br> <Transition mode="out-in"> <img src="/img_pizza.svg" v-if="imgActive === 'pizza'"> <img src="/img_apple.svg" v-else-if="imgActive === 'apple'"> <img src="/img_cake.svg" v-else-if="imgActive === 'cake'"> <img src="/img_fish.svg" v-else-if="imgActive === 'fish'"> <img src="/img_rice.svg" v-else-if="imgActive === 'rice'"> </Transition></template><script>export default { data() { return { imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'], indexNbr: 0 } }, computed: { imgActive() { if(this.indexNbr >= this.imgs.length) { this.indexNbr = 0; } return this.imgs[this.indexNbr]; } }}</script><style> .v-enter-active { animation: swirlAdded 0.7s; } .v-leave-active { animation: swirlAdded 0.7s reverse; } @keyframes swirlAdded { from { opacity: 0; rotate: 0; scale: 0.1; } to { opacity: 1; rotate: 360deg; scale: 1; } } img { width: 100px; margin: 20px; } img:hover { cursor: pointer; }</style>Run Example »Transition with Dynamic Components
We can also use the<Transition> component to animate switching between dynamic components:
Example
App.vue:
<template> <h1>Transition with Dynamic Components</h1> <p>The Transition component wraps around the dynamic component so that the switching can be animated.</p> <button @click="toggleValue = !toggleValue">Switch component</button> <Transition mode="out-in"> <component :is="activeComp"></component> </Transition></template><script> export default { data () { return { toggleValue: true } }, computed: { activeComp() { if(this.toggleValue) { return 'comp-one' } else { return 'comp-two' } } } }</script><style> .v-enter-active { animation: slideIn 0.5s; } @keyframes slideIn { from { translate: -200px 0; opacity: 0; } to { translate: 0 0; opacity: 1; } } .v-leave-active { animation: slideOut 0.5s; } @keyframes slideOut { from { translate: 0 0; opacity: 1; } to { translate: 200px 0; opacity: 0; } } #app { width: 350px; margin: 10px; } #app > div { border: solid black 2px; padding: 10px; margin-top: 10px; }</style>Run Example »
