Vue.js Transition
Vue.js provides a <transition> component which wraps the element or component you want to animate. It automatically adds three entering and three leaving CSS classes.
v-enter
, v-enter-active
, and v-enter-to
are used for the enter transition when the component is inserted or enabled.
v-leave
, v-leave-active
, and v-leave-to
are used for the leave transition when the component is disabled or removed.
v-enter /v-leave and v-enter-to /v-leave-to define the starting and ending states of the transition.
v-enter-active /v-leave-active define the transition's configuration, such as duration, easing function, etc.
The <transition> component can have a name attribute. If we define one, it will replace the default v- prefix in all classes.
For example, if the name is set to "tab", the v-enter
class will become tab-enter
, and so on.
- Syntax:
<div id='demo'>
<transition name='test'>
<div>Content</div>
</transition>
</div>
In the element added in
<transition> we can use
v-if
and
v-show
for entering /leaving transitions. Then, in CSS style we apply properties for transition /animation to the CSS classes added by Vue.
- Here is a simple example with a button and a H4 tag to which we apply transition:
<style>
.cls_h {
color:#00e;
}
.test-enter-active, .test-leave-active {
transition: opacity 1s;
}
.test-enter, .test-leave-to {
opacity: 0;
}
</style>
<div id='demo'>
<transition name='test'>
<h3 v-show ='show' class='cls_h'>To have peace I transmit peace.</h3>
</transition>
<button v-on:click='show = !show'>Show /Hide</button>
</div>
<script>
var vm = new Vue({
el: '#demo',
data:{ show:false }
});
</script>
In the example above we apply css style for transition effect to the classes defined by Vue, but with the prefix "
test" (the name of the <transition> tag):
.test-enter-active, .test-leave-active, .test-enter, .test-leave-to.
Transition on Components
In the <transition>
element we can add custom Vue components.
- In the following example it is defined a Vue component (here "comp_test") and included in <transition>:
<style>
.cls_h {
color:#00e;
}
.test-enter-active, .test-leave-active {
transition: opacity 1s;
}
.test-enter, .test-leave-to {
opacity: 0;
}
</style>
<div id='demo'>
<transition name='test'>
<comp_test v-show='show'></comp_test>
</transition>
<button v-on:click ='show = !show'>Show /Hide</button>
</div>
<script>
Vue.component('comp_test', {
props: ['show'],
data: function(){
return {msg:'Giving and receiving are one; I give what I want to receive.'}
},
template: "<h3 class='cls_h'>{{msg}}</h3>"
});
var vm = new Vue({
el: '#demo',
data:{show:false}
});
</script>
Transition with v-if and v-else
You can also transition between raw elements using v-if
and v-else
.
- Example:
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
<div id='demo'>
<transition name='fade'>
<h3 v-if='rest ==1'>This is in the tag with v-if, rest ={{rest}}.</h3>
<div v-else-if='rest ==2'>This is from v-else-if, rest ={{rest}}.</div>
<h4 v-else>This content is from v-else, rest not 1 or 2; rest ={{rest}}.</h4>
</transition>
Click to <button @click='altrElm'>Alternate elements</button>
</div>
<script>
var vm = new Vue({
el: '#demo',
data:{ rest:0, nr:0 },
methods:{
altrElm:function(){
this.nr++;
this.rest = this.nr %3;
}
}
});
</script>
When toggling between elements that have the same tag name, you must tell Vue that they are distinct elements by giving them unique key attributes. Otherwise, Vue's compiler will only replace the content of the element.
It's indicated to always add a key attribute when there are multiple items within a <transition> component.
- Example Syntax:
<transition>
<button v-if='is_save' key='save'>Save</button>
<button v-else key='edit'>Edit</button>
</transition>
Transition Modes
It's possible to transition between any number of elements, either by using multiple v-ifs or binding a single element to a dynamic property.
In the <transition> tag you can add a mode
attribute to get different transition effect.
The mode attribute can have one of these two values: in-out
and out-in
.
- Test the example below and change "out-in" with "in-out" to see the difference.
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
<div id='demo'>
Example with multiple <b>v-if</b>, without <b>mode</b> attribute:
<transition name='fade'>
<button @click='bState' v-if='b_state =="saved"' key='saved'>Save</button>
<button @click='bState' v-if='b_state =="edited"' key='edited'>Edit</button>
</transition>
<br><br>
Example with a single element binded to a dynamic property (here is_save), and <b>mode</b> attribute:
<transition name='fade' mode='out-in'>
<button @click='btnState' v-bind:key='is_save'>{{ btn_txt }}</button>
</transition>
</div>
<script>
var vm = new Vue({
el: '#demo',
data:{ b_state:'saved', btn_txt:'Save', is_save:true},
methods:{
//for example with v-if
bState: function(ev){
this.b_state = (this.b_state =='saved') ? 'edited' :'saved';
},
//for example with single element binded to is_save
btnState: function(ev){
this.is_save = !this.is_save;
this.btn_txt = this.is_save ? 'Save' :'Edit';
}
}
});
</script>
CSS Animation with Vue js
CSS animations are applied in the same way as CSS transitions, the difference being that v-enter
is not removed after the element is inserted, but on an animationend event.
- Here is an example to see how animation works.
<style>
.cls_d {
background:#dadafe;
font-size:20px;
height:100px;
text-align:center;
width:180px;
}
@keyframes ex_anim {
0% { background:#fe1234; width:0;height:0; font-size:0;}
50% {
border-radius: 100%;
transform: rotate(180deg);
}
100% {
background:#bbbbfe;
transform: rotate(360deg);
transform: scale(1.05);
font-size:8px;
}
}
.ro_x-enter-active {
animation-name: ex_anim;
animation-duration: 1.5s;
}
.ro_x-leave-active {
animation: ex_anim reverse;
animation-duration: 1.5s;
}
</style>
<div id='demo'>
<transition name='ro_x'>
<div v-show ='show' class='cls_d'>The past disappears, the moment remains.<br>Who am I?</div>
</transition>
<button v-on:click ='show = !show'>Show /Hide</button>
</div>
<script>
var vm = new Vue({
el: '#demo',
data:{ show:true }
});
</script>
Transition at the Initial Render
If you want to add animation at the start, you need to add the appear
attribute in the transition tag.
- Here is the same animation example, but with the appear attribute. In this case we not need the v-show
condition.
<style>
.cls_d {
background:#dadafe;
font-size:20px;
height:100px;
text-align:center;
width:180px;
}
@keyframes ex_anim {
0% { background:#fe1234; width:0;height:0; font-size:0;}
50% {
border-radius: 100%;
transform: rotate(180deg);
}
100% {
background:#bbbbfe;
transform: rotate(360deg);
transform: scale(1.05);
font-size:8px;
}
}
.ro_x-enter-active {
animation-name: ex_anim;
animation-duration: 2s;
}
</style>
<div id='demo'>
<transition name='ro_x' appear>
<div class='cls_d'>The past disappears, the moment remains.<br>Who am I?</div>
</transition>
</div>
<script>
var vm = new Vue({
el: '#demo'
});
</script>
Custom Transition Classes
Vue.JS provides a list attributes that can be added to the <transition> element to can define custom classes for CSS transition.
- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class
These attributes will override the conventional class names.
Custom classes are useful when we want to use an external CSS library such as animate.css.
- Example, we use
enter-active-class and
leave-active-class attributes to set custom CSS classes for transition (classes used with animate.css:
animated, tada, bounceOutRight):
<link href='//cdn.jsdelivr.net/npm/animate.css@3.5.1' rel='stylesheet' type='text/css'>
<div id='demo' style='text-align:center'>
<button @click='show = !show'>Toggle render</button>
<transition name='custom_cls' enter-active-class='animated tada' leave-active-class='animated bounceOutRight'>
<h3 v-if='show'>Love Life</h3>
</transition>
</div>
<script>
var vm = new Vue({
el: '#demo',
data: { show: true }
});
</script>
Explicit Transition Duration
Vue waits for the transitionend and animationend event to detect if the animation or transition is done. Sometimes it can cause delay.
In such cases you can specify an explicit transition duration (in milliseconds) by binding the :duration
property in the <transition> tag:
<transition :duration='1000'>...</transition>
Or with separate values for enter and leave durations:
<transition :duration='{ enter:500, leave:800 }'>...</transition>
Transition classes called as Methods
The transition classes can be called as methods using JavaScript Hooks attributes, defined with v-on:
prefix and the name of the event to which the method is called.
- Syntax:
<transition
v-on:before-enter='beforeEnter' v-on:enter='enter'
v-on:after-enter='afterEnter' v-on:enter-cancelled='enterCancelled'
v-on:before-leave='beforeLeave' v-on:leave='leave'
v-on:after-leave='afterLeave' v-on:leave-cancelled='leaveCancelled'
>
HTML Elements
</transition>
These hooks can be used in combination with CSS transitions /animations or on their own.
When using JavaScript-only transitions, a callback function is required for the enter and leave methods (see in the example below). Otherwise, the method will be called synchronously and the transition will finish immediately.
It's good to add v-bind:css='false'
for JavaScript-only transitions so that Vue can skip the CSS detection. This also prevents CSS rules from interfering with the transition.
Example, using
Vue and velocity.js. We perform animation with js methods on the <transition> element.
<div id='demo' style='text-align:center'>
<button @click='show = !show'>Toggle render</button>
<transition v-on:before-enter='beforeEnter' v-on:enter='enter' v-on:leave='leave' v-bind:css='false'>
<h3 v-if='show'>Love Life</h3>
</transition>
</div>
<script>
document.write('<sc'+'ript src="//cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></sc'+'ript>');
var vm = new Vue({
el: '#demo',
data: { show: false },
methods: {
beforeEnter: function(el){
el.style.opacity = 0
},
//done callback is optional when used in combination with CSS
enter: function(el, done){
Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
Velocity(el, { fontSize: '1em' }, { complete: done })
},
leave: function(el, done){
Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 });
Velocity(el, { rotateZ: '100deg' }, { loop: 2 });
Velocity(el, { rotateZ: '45deg', translateY: '30px', translateX: '30px', opacity: 0 }, { complete: done });
}
}
});
</script>