May 07, 2021 Vue.js 2.0
1. Vue.js 2.0 Transition Effect Overview
2. Transition of a single element/component
3. The transition to the initial rendering
4. The transition of multiple elements
Vue provides a variety of different ways to make app transitions when inserting, updating, or removing DOMs. Includes the following tools:
Here, we'll just talk about transitions to entry, departure, and lists, and you can also look at the "Managing Transition State" in the next section.
Vue provides the encapsulation components for transition, and in the following cases, you can add an enter/lefting transition to any element and component
Here is a typical example:
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#demo',
data: {
show: true
}
})
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-active {
opacity: 0
}
hello
After the element is encapsulated as a staging component, Vue will be inserted or deleted
There are 4 (CSS) class names that switch between enter/leave transitions
For these class names that switch in the enter/leave transition, v- is the prefix for those class names. You can reset the prefix, such as v-enter, by replacing it with my-transition-enter.
v-enter-active and v-leave-active can control the different stages of the in/out transition, as shown in the following sections.
Common transitions are available using CSS transitions.
Here's a simple example:
<div id="example-1">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#example-1',
data: {
show: true
}
})
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-active {
padding-left: 10px;
opacity: 0;
}
hello
CSS animation usage transitions to CSS, with the difference that the v-enter class name in the animation is not deleted immediately after the node inserts the DOM, but when the animationend event is triggered.
Example: (compatibility prefix omitted)
<div id="example-2">
<button @click="show = !show">Toggle show</button>
<transition name="bounce">
<p v-if="show">Look at me!</p>
</transition>
</div>
new Vue({
el: '#example-2',
data: {
show: true
}
})
.bounce-enter-active {
animation: bounce-in .5s;
}
.bounce-leave-active {
animation: bounce-out .5s;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
@keyframes bounce-out {
0% {
transform: scale(1);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(0);
}
}
Look at me!
We can customize the transition class name with the following features:
Their priority is higher than normal class names, which is useful for Vue's transition system in conjunction with other third-party CSS animation libraries .css such as Animate.
Example:
<link href="https://unpkg.com/[email protected]/animate.min.css" rel="external nofollow" target="_blank" rel="stylesheet" type="text/css">
<div id="example-3">
<button @click="show = !show">
Toggle render
</button>
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#example-3',
data: {
show: true
}
})
hello
In order for Vue to know that the transition is complete, the appropriate event listener must be set up. I t can be transitionend or animationend, depending on the CSS rules applied to the element. If you use 1 of these, Vue automatically identifies the type and sets the listening.
However, in some scenarios, you need to set two transition dynamics for the same element at the same time, such as animation being triggered and completed quickly, and the transition effect is not over yet. In this case, you need to use the type attribute and set animation or transition to explicitly state the type of Vue listening you need.
JavaScript hooks can be declared in properties
<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"
>
<!-- ... -->
</transition>
These hook functions can be used in conjunction with CSS
transitions/animations
or they can be used separately.
In enter and leave, the callback function done is a must when only JavaScript transitions are used. Otherwise, they are called synchronously and the transition is completed immediately.
It is recommended to add v-bind:css="false" for elements that use only JavaScript transitions, and Vue skips detection of CSS. This also avoids the impact of CSS during the transition.
A simple example of .js Velocity:
<!--
Velocity works very much like jQuery.animate and is
a great option for JavaScript animations
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
<div id="example-4">
<button @click="show = !show">
Toggle
</button>
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
>
<p v-if="show">
Demo
</p>
</transition>
</div>
new Vue({
el: '#example-4',
data: {
show: false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
},
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 })
}
}
})
You can set the transition of the node in the initial rendering through the see attribute:
<transition appear>
<!-- ... -->
</transition>
The CSS class name can also be customized here by default, as is the entry and departure transition.
<transition
appear
appear-class="custom-appear-class"
appear-active-class="custom-appear-active-class"
>
<!-- ... -->
</transition>
Custom JavaScript hooks:
<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
>
<!-- ... -->
</transition>
We'll discuss "Transitions for Multiple Components" later in this chapter, where you can use v-if/v-else for native labels. The most common multi-label transition is a list and an element that describes an empty message for this list:
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>
This can be used, but there is one thing to note:
When elements with the same label name switch, you need to set unique values through the key attribute to mark them to allow Vue to distinguish them, otherwise Vue will only replace the contents of the same label for efficiency. E ven if it's not technically necessary, it's a better practice to set key for multiple elements in a component.
Example:
<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
In some scenarios, v-if and v-else can also be replaced by setting different states for the key attribute of the same element, and the above example can be override as:
<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
Transitions that use multiple elements of v-if can be overrideed as a single element transition bound to dynamic properties. For example:
<transition>
<button v-if="docState === 'saved'" key="saved">
Edit
</button>
<button v-if="docState === 'edited'" key="edited">
Save
</button>
<button v-if="docState === 'editing'" key="editing">
Cancel
</button>
</transition>
Can be rewritten as:
<transition>
<button v-bind:key="docState">
{{ buttonMessage }}
</button>
</transition>
// ...
computed: {
buttonMessage: function () {
switch (docState) {
case 'saved': return 'Edit'
case 'edited': return 'Save'
case 'editing': return 'Cancel'
}
}
}
Here's another question, try clicking on the button below:
In the transition between the "on" button and the "off" button, both buttons are redrawn, and one leaves the transition and the other begins to transition. This is the default behavior of slt;transitions - entry and departure occur at the same time.
Works fine when the elements are absolutely positioned above each other:
Then, let's add translate to make them move like sliding transitions:
The transition of entry and departure that takes effect at the same time does not meet all requirements, so Vue provides a transition mode
Transition with the switch button before override with out-in:
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
Adding a simple feature solves the previous transition problem without any additional code.
In-out mode is not often used, but it is useful for slightly different transition effects. Combine the examples of previous sliding fades with:
Isn't that cool?
The transition between multiple components is much simpler - we don't need to use the key feature. Instead, we only need to use dynamic components:
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
new Vue({
el: '#transition-components-demo',
data: {
view: 'v-a'
},
components: {
'v-a': {
template: '<div>Component A</div>'
},
'v-b': {
template: '<div>Component B</div>'
}
}
})
So far, we've talked about transition:
So how do you render the entire list at the same time, such as using v-for? I n this scenario, use the components of the .lt;transition-group. Before we dive into the example, let's look at a few features of this component:
Now let's drill down from a simple example to enter and leave the transition using the same CSS class name as before.
<div id="list-demo" class="demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
new Vue({
el: '#list-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
}
})
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-active {
opacity: 0;
transform: translateY(30px);
}
Results:
123456789
One problem with this example is that when you add and remove elements, the surrounding elements move instantaneously to the position of their new layout, rather than a smooth transition, which we'll fix below.
There's one special thing about the components. N ot only can you enter and leave the animation, but you can also change the positioning. T o use this new feature, you only need to understand the new v-move feature, which is applied as the element changes its positioning. As with the previous class name, you can customize the prefix with the name property, or you can set it manually with the move-class property.
v-move is useful for setting transition switching times and transition curves, and you'll see the following examples:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js" rel="external nofollow" rel="external nofollow" ></script>
<div id="flip-list-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<transition-group name="flip-list" tag="ul">
<li v-for="item in items" v-bind:key="item">
{{ item }}
</li>
</transition-group>
</div>
new Vue({
el: '#flip-list-demo',
data: {
items: [1,2,3,4,5,6,7,8,9]
},
methods: {
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
})
.flip-list-move {
transition: transform 1s;
}
This looks amazing, with the internal implementation, Vue using a simple animation queue called FLIP to use transforms to smooth the transition of elements from their previous positions to new locations.
We combine the examples we implemented earlier with this technique so that all changes in our list will have an animated transition.
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js" rel="external nofollow" rel="external nofollow" ></script>
<div id="list-complete-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list-complete" tag="p">
<span
v-for="item in items"
v-bind:key="item"
class="list-complete-item"
>
{{ item }}
</span>
</transition-group>
</div>
new Vue({
el: '#list-complete-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
})
.list-complete-item {
transition: all 1s;
display: inline-block;
margin-right: 10px;
}
.list-complete-enter, .list-complete-leave-active {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
Results:
123456789
It is important to note that elements that use the FLIP transition cannot be set to display: inline. As an alternative, you can set it to display: inline-block or place it in flex.
FLIP animations not only enable single-column transitions, but also multi-dimensional mesh transitions
By communicating with JavaScript through the data property, you can make a gradual transition to the list:
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
<div id="staggered-list-demo">
<input v-model="query">
<transition-group
name="staggered-fade"
tag="ul"
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
<li
v-for="(item, index) in computedList"
v-bind:key="item.msg"
v-bind:data-index="index"
>{{ item.msg }}</li>
</transition-group>
</div>
new Vue({
el: '#staggered-list-demo',
data: {
query: '',
list: [
{ msg: 'Bruce Lee' },
{ msg: 'Jackie Chan' },
{ msg: 'Chuck Norris' },
{ msg: 'Jet Li' },
{ msg: 'Kung Fury' }
]
},
computed: {
computedList: function () {
var vm = this
return this.list.filter(function (item) {
return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
})
}
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
el.style.height = 0
},
enter: function (el, done) {
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(
el,
{ opacity: 1, height: '1.6em' },
{ complete: done }
)
}, delay)
},
leave: function (el, done) {
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(
el,
{ opacity: 0, height: 0 },
{ complete: done }
)
}, delay)
}
}
})
Results:
Transitions can be reused through Vue's component system. To create a re-usable staging component, all you need to do is use the .lt;transition> or the .lt;transition-group-gt; as the root component, and then place any sub-components in it.
A simple example of using template:
Vue.component('my-special-transition', {
template: '\
<transition\
name="very-special-transition"\
mode="out-in"\
v-on:before-enter="beforeEnter"\
v-on:after-enter="afterEnter"\
>\
<slot></slot>\
</transition>\
',
methods: {
beforeEnter: function (el) {
// ...
},
afterEnter: function (el) {
// ...
}
}
})
Function components are better suited for this task:
Vue.component('my-special-transition', {
functional: true,
render: function (createElement, context) {
var data = {
props: {
name: 'very-special-transition'
mode: 'out-in'
},
on: {
beforeEnter: function (el) {
// ...
},
afterEnter: function (el) {
// ...
}
}
}
return createElement('transition', data, context.children)
}
})
Even transitions are data-driven in Vue! The most basic example of a dynamic transition is to bind dynamic values through the name attribute.
<transition v-bind:name="transitionName">
<!-- ... -->
</transition>
It is useful to switch between transitions when you want to define CSS transitions/animations with Vue's transition system.
All transition features are dynamically bound. I t is not only a simple feature, through the hook function method of the event, you can get the appropriate context data. This means that different transition effects can be set through the JavaScript transition depending on the state of the component.
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
<div id="dynamic-fade-demo">
Fade In: <input type="range" v-model="fadeInDuration" min="0" v-bind:max="maxFadeDuration">
Fade Out: <input type="range" v-model="fadeOutDuration" min="0" v-bind:max="maxFadeDuration">
<transition
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
<p v-if="show">hello</p>
</transition>
<button v-on:click="stop = true">Stop it!</button>
</div>
new Vue({
el: '#dynamic-fade-demo',
data: {
show: true,
fadeInDuration: 1000,
fadeOutDuration: 1000,
maxFadeDuration: 1500,
stop: false
},
mounted: function () {
this.show = false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
},
enter: function (el, done) {
var vm = this
Velocity(el,
{ opacity: 1 },
{
duration: this.fadeInDuration,
complete: function () {
done()
if (!vm.stop) vm.show = false
}
}
)
},
leave: function (el, done) {
var vm = this
Velocity(el,
{ opacity: 0 },
{
duration: this.fadeOutDuration,
complete: function () {
done()
vm.show = true
}
}
)
}
}
})
Results:
hello