过度和动画效果
Vue.js内置了一套过渡系统,该系统是Vue.js为DOM动画效果提供的一个特性。它在插入、更新或者移除DOM时可以触发CSS过渡和动画,从而产生过渡效果。
1、单元素过渡
1.1、CSS过渡
Vue.js提供了一个内置的封装组件transition,该组件可以为其中包含的DOM元素实现过渡效果。过渡封装组件的语法格式如下:
html
<transition name = "nameoftransition">
<div></div>
</transition>
上述语法中,nameoftransition参数用于自动生成CSS过渡类名。
在下列情形中,可以为元素和组件添加过渡效果:
- 条件渲染(使用v-if指令)。
- 条件展示(使用v-show指令)。
- 动态组件。
- 组件根节点。
下面通过一个示例来说明CSS过渡的基础用法。示例代码如下:
html
<style>
/*设置CSS属性名和持续时间 */
.effect-enter-active, .effect-leave-active{
transition: opacity 1s
}
.effect-enter-from, .effect-leave-to {
opacity: 0
}
</style>
<div id="app">
<button @click="show = !show">{{show ? '隐藏' : '显示'}}</button><br>
<transition name="effect">
<p v-if="show">工具善其事,必先利其器。</p>
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
show: true
}
}
});
vm.mount('#app');
</script>

上述代码中,通过单击"隐藏"或"显示"按钮使变量show的值在true和false之间进行切换。show的值如果为true则淡入显示文本,如果为false则淡出隐藏文本。
CSS过渡其实就是一个淡入淡出的效果。当插入或删除包含在transition组件中的元素时,Vue.js将执行以下操作:
- 自动检测目标元素是否应用了CSS过渡或动画,如果是,则在合适的时机添加或删除CSS类名。
- 如果过渡组件提供了JavaScript钩子函数,这些钩子函数将在合适的时机被调用。
- 如果没有找到JavaScript钩子,也没有检测到CSS过渡或动画,则DOM操作(插入或删除)将在下一帧中立即执行。
1.2、过度的类名
Vue.js在元素显示与隐藏的过渡效果中,提供了6个class类名来切换。这些类名的具体说明如表所示。
| class类名 | 说明 |
|---|---|
v-enter-from |
定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除 |
v-enter-active |
定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡或动画完成之后移除。这个类可以用来定义进入过渡的过程时间、延迟和曲线函数 |
v-enter-to |
定义进入过渡的结束状态。在元素被插入之后的下一帧生效(与此同时v-enter-fom被移除),在过渡或动画完成之后移除 |
v-leave-from |
定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除 |
v-leave-active |
定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡或动画完成之后移除。这个类可以用来定义离开过渡的过程时间、延迟和曲线函数 |
v-leave-to |
定义离开过渡的结束状态。在离开过渡被触发之后的下一帧生效(与此同时v-leave-from被移除),在过渡或动画完成之后移除 |
如果没有为<transition>设置一个名字,则v-是这些类名的默认前缀。如果为<transition>设置了一个名字,如<transition name="my">,则v-enter-from会替换为my-enter-from。
示例:切换图片的过渡效果。
在页面中设计一个切换图片的过渡效果,当单击页面中的图片时会切换为另一张图片,在切换时有一个过渡效果。关键代码如下:
html
<style>
/*设置过度属性*/
.effect-enter-active, .effect-leave-active{
transition: all .5s ease;
}
.effect-enter-from, .effect-leave-to {
opacity: 0
}
</style>
<div id="app">
<transition name="effect">
<img v-if="show" src="images/1.jpg" @click="show = !show">
<img v-if="!show" src="images/2.jpg" @click="show = !show">
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
show: true
}
}
});
vm.mount('#app');
</script>
1.3、自定义过度的类名
除了使用普通的类名(如*-enter-from、-leave-from除了使用普通的类名(如-enter-from、*-leave-from等),Vue.js也允许自定义过渡类名。自定义过渡类名可以通过以下6个属性进行定义:
- enter-from-class
- enter-active-class
- enter-to-class
- leave-from-class
- leave-active-class
- leave-to-class
自定义过渡的类名的优先级高于普通的类名。通过自定义过渡类名可以使过渡系统和其他第三方CSS动画库(如animate.css)相结合,实现更丰富的动画效果。
下面通过一个实例来了解自定义过渡类名的使用。该实例需要应用第三方CSS动画库文件animate.css。
示例:实现文字显示和隐藏的动画效果。
页面中有一个按钮和一行文字,每次单击按钮都会使文字在显示和隐藏之间进行切换,在隐藏文字时使用向右弹出的动画效果,在显示文字时使用向上弹跳的动画效果。关键代码如下:
html
<style>
.container{
width: 500px;
margin: 20px auto;
text-align: center;
}
p{
font: 30px "微软雅黑";
margin: 50px auto;
font-weight: 500;
color: blue;
}
</style>
<div id="app">
<button @click="show = !show">{{show ? '隐藏' : '显示'}}</button>
<transition name="effect" enter-active-class="animated bounceInUp"
leave-active-class="animated bounceOutRight">
<p v-if="show">锲而不舍,金石可镂。</p>
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
show: true
}
}
});
vm.mount('#app');
</script>
运行实例,当单击"隐藏"按钮时,文本会以向右弹出的动画形式进行隐藏,同时按钮文字变为"显示"。再次单击该按钮,文本会以向上弹跳的动画形式进行显示,同时按钮文字变为"隐藏"。
1.4、CSS动画
CSS动画的用法和CSS过渡类似。不同的是在动画中,在节点插入DOM后不会立即删除v-enter-from类名,而是在animationend事件触发时删除。下面通过一个实例来了解一下CSS动画的应用。
示例:图片的旋转动画效果。
页面中有一个"隐藏图片"按钮和一张图片。每次单击按钮都会以旋转的动画形式隐藏或显示图片,同时按钮文字会在"显示图片"和"隐藏图片"之间进行切换。关键代码如下:
html
<style>
.container{
width: 500px;
margin: 20px auto;
}
button{
margin-bottom: 30px;
}
/* 设置animation属性的参数 */
.effect-enter-active{
animation: effect 1s reverse
}
.effect-leave-actibe{
animation: effect 1s
}
/* 设置元素的旋转 */
@keyframes effect{
0% {
transform: rotate(0);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<div id="app">
<div class="container">
<button @click="show = !show">{{show ? '隐藏图片' : '显示图片'}}</button><br>
<transition name="effect">
<img :src="url" v-if="show">
</transition>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
url: 'images/1.jpg',
show: true
}
}
});
vm.mount('#app');
</script>
运行实例,当单击"隐藏图片"按钮时,图片会以旋转的动画形式进行隐藏。再次单击该按钮,图片会以旋转的动画形式进行显示。

1.5、使用JavaScript钩子函数实现动画
设置元素的过渡效果还有一种方式,就是使用JavaScript钩子函数。在钩子函数中可以直接操作DOM元素。在<transition>元素的属性中声明钩子函数,代码如下:
html
<transition>
v-on:befor-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>
<div id="app">
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
}
},
methods: {
//设置进入过度之前的组件状态
beforeEnter: (el) => {
// ...
},
//设置进入过度完成时的组件状态
enter: function(el, done) {
//...
done()
},
//设置进入过度完成之后的组件状态
afterEnter: function(el) {
//...
},
enterCancelled: function(el) {
///...
},
//设置离开过度之前的组件状态
beforeLeave: function(el) {
//...
},
//设置离开过度完成时的组件状态
leave: function(el, done) {
//...
done()
},
//设置离开过度完成之后的组件状态
afterLeave: function(el) {
//...
},
leaveCancelled: function(el) {
//...
}
}
});
vm.mount('#app');
</script>
这些钩子函数可以结合CSS过渡或动画使用,也可以单独使用。<transition>元素还可以添加v-bind:css="false",它的作用是直接跳过CSS检测,避免CSS在过渡过程中的影响。
当只使用JavaScript过渡时,在enter和leave钩子函数中必须使用done进行回调。否则,它们将被同步调用,过渡会立即完成。
示例:实现图片显示和隐藏的动画效果。
页面中有一个"显示图片"按钮和一张图片,每次单击按钮会实现图片显示和隐藏的动画效果。显示图片使用旋转的效果,隐藏图片使用缩放动画的效果,同时按钮文字会在"显示图片"和"隐藏图片"之间进行切换。关键代码如下:
html
<style>
.container{
width: 500px;
margin: 20px auto;
text-align: center;
}
button{
margin-bottom: 50px;
}
/*设置缩放转换*/
@keyframes scaling {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(0);
}
}
/*创建旋转动画*/
@-webkit-keyframes rotate{
0%{
-webkit-transform:rotateZ(0) scale(0);
}
50% {
-webkit-transform:rotateZ(360deg) scale(0.5);
}
100%{
-webkit-transform:rotateZ(720deg) scale(1);
}
}
</style>
<div id="app">
<div class="container">
<button @click="show = !show">{{show ? '隐藏图片' : '显示图片'}}</button><br>
<transition
@enter="enter"
@leave="leave"
@after-leave="afterLeave">
<img :src="url" v-if="show">
</transition>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
url: 'images/1.jpg',
show: false
}
},
methods: {
enter: function(el, done) {
el.style.opacity = 1;
el.style.animation = 'rotate 2s linear';
done();
},
leave: function(el, done) {
el.style.animation = 'scaling 1.5s';
setTimeout(()=> {
done();
}, 1500);
},
//在leave函数中触发回调后执行afterLeave函数
afterLeave: function(el) {
el.style.opacity = 0;
}
}
});
vm.mount('#app');
</script>
运行实例,当单击"显示图片"按钮时,图片会以旋转的形式进行显示,结果如图所示。

再次单击该按钮,图片会以缩放动画的形式进行隐藏,结果如图所示。

2、多元素过度
2.1、多元素过渡的用法
两个或两个以上元素的过渡就是多元素过渡。最常见的多元素过渡是一个列表和描述这个列表为空的元素之间的过渡。在实现多元素过渡的效果时可以使用v-if和v-else指令。示例代码如下:
html
<style>
ul, li{
padding: 0;
}
ul {
line-height: 26px;
}
/*设置过度属性*/
.effect-enter-from, .effect-leave-to{
opacity: 0;
}
.effect-enter-active, .effect-leace-active{
transition: opacity 1s;
}
</style>
<div id="app">
<button @click="clearArr">清空</button>
<transition name="effect">
<ol v-if="items.length > 0">
<li v-for="item in items">{{item}}</li>
</ol>
<p v-else>内容为空</p>
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
items: [
'白日依山尽,',
'黄河入海流。',
'欲穷千里目,',
'更上一层楼。'
]
}
},
methods: {
clearArr: function() {
this.items.splice(0);
}
}
});
vm.mount('#app');
</script>
运行上述代码,当单击"清空"按钮时,列表内容会被清空。在页面内容发生变化时会有一个过渡的效果,结果如图所示。


2.3、设置元素的key属性
当有相同标签名的多个元素进行切换时,需要通过key属性设置唯一的值来标记以让Vue区分它们。示例代码如下:
html
<style>
/*设置过度属性*/
.effect-enter-from, .effect-leave-to{
opacity: 0;
}
.effect-enter-active, .effect-leave-active{
transition:opacity 1s;
}
</style>
<div id="app">
<button @click="show = !show">切换</button>
<transition name="effect">
<p v-if="show" key="one">一寸光阴一寸金,</p>
<p v-else key="two">寸金难买寸光阴。</p>
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
show: true
}
},
});
vm.mount('#app');
</script>
运行上述代码,单击"切换"按钮,下方的内容会发生变化,在变化时会有一个过渡的效果。

在一些场景中,可以将同一个元素的key属性绑定到一个动态属性,通过设置不同的状态来代替v-if和v-else。将上述代码进行修改,代码如下:
html
<style>
/*设置过度属性*/
.effect-enter-from, .effect-leave-to{
opacity: 0;
}
.effect-enter-active, .effect-leave-active{
transition:opacity 1s;
}
</style>
<div id="app">
<button @click="show = !show">切换</button>
<transition name="effect">
<p :key="show">
{{show ? '一寸光阴一寸金,' : '寸金难买寸光阴。'}}
</p>
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
show: true
}
},
});
vm.mount('#app');
</script>
运行上述代码,每次单击"切换"按钮都会切换不同的内容,在页面内容发生变化时会有一个过渡的效果,结果如图所示。

上述示例代码可以重写为绑定了动态属性的单个元素过渡。修改后的代码如下:
html
<style>
/*设置过度属性*/
.effect-enter-from, .effect-leave-to{
opacity: 0;
}
.effect-enter-active, .effect-leave-active{
transition:opacity 1s;
}
</style>
<div id="app">
<button @click="toggle">切换</button>
<transition name="effect">
<p :key="getState">
{{text}}
</p>
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
index: 0,
arr: ['one', 'two', 'three']
}
},
methods: {
toggle: function() {
this.index = (++this.index) % 3;
}
},
computed: {
getState: function() {
return this.arr[this.index];
},
text: function() {
switch(this.getState) {
case 'one':
return '慈母手中线,游子身上衣。'
case 'two':
return '临行密密缝,意恐迟迟归。'
case 'three':
return '谁言寸草心,报得三春晖!'
}
}
}
});
vm.mount('#app');
</script>

2.3、过度模式的设置
使用<transition>组件实现过渡效果,在默认情况下,元素的进入和离开是同时发生的。这种情况并不能满足所有需求,所以Vue.js提供了如下两种过渡模式:
in-out:新元素先进行过渡,完成之后当前元素过渡离开。out-in:当前元素先进行过渡,完成之后新元素过渡进入。
例如,应用out-in模式实现文字切换时的过渡效果,代码如下:
html
<style>
/*设置过度属性*/
.effect-enter-from, .effect-leave-to{
opacity: 0;
}
.effect-enter-active, .effect-leave-active{
transition:opacity 1s;
}
</style>
<div id="app">
<transition name="effect" mode="out-in">
<div @click="show = !show" :key="show">
<p v-if="show">不积跬步,无以至千里。</p>
<p v-else>不积小流,无以至江海。</p>
</div>
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
show: true
}
}
});
vm.mount('#app');
</script>
运行上述代码,每次单击页面中的文字都会切换为另一行文字。在切换时有一个过渡效果,而且在当前的文字完成过渡效果之后才会显示新的文字。结果如图所示。

3、多组件过渡
多个组件的过渡不需要为每个组件设置key属性,只需要使用动态组件即可。例如,实现两个组件切换时的过渡效果,代码如下:
html
<style>
label{
margin-right: 10px;
}
/*设置过度属性*/
.effect-enter-from, .effect-leave-to{
opacity: 0;
}
.effect-enter-active, .effect-leave-active{
transition:opacity 1s;
}
</style>
<div id="app">
<button @click="toggle">切换</button>
<transition name="effect" mode="out-in">
<component :is="cName"></component>
</transition>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
cName: 'interest'
}
},
components: {
interest: {
template: `<div>
<p>请选择兴趣爱好:</p>
<input type="checkbox" id="book" value="看书">
<label for="book" >看书</label>
<input type="checkbox" id="music" value="听音乐">
<label for="music" >听音乐</label>
<input type="checkbox" id="travel" value="旅游">
<label for="travel" >旅游</label>
</div>`
},
sport: {
template: `<div>
<p>请选择运动项目:</p>
<input type="checkbox" id="run" value="跑步">
<label for="run" >跑步</label>
<input type="checkbox" id="basketball" value="打篮球">
<label for="basketball" >打篮球</label>
<input type="checkbox" id="football" value="踢足球">
<label for="football" >踢足球</label>
</div>`
},
},
methods: {
toggle: function(){
this.cName = this.cName === 'interest' ? 'sport' : 'interest';
}
}
});
vm.mount('#app');
</script>
运行上述代码,每次单击"切换"按钮都会在两个组件之间进行切换,在页面内容发生变化时都会有一个过渡的效果,结果如图所示。


示例:实现切换图书类别选项卡的过渡效果。
页面中有"HTML5+CSS3""JavaScript""Java Web""Android"和"Java"5个图书类别选项卡,单击不同的类别选项卡,右侧会显示不同的图片,在内容发生变化时会有一个过渡的效果。实现步骤如下。
- 定义<div>元素,并设置其id属性值为app,在该元素中定义5个图书类别选项卡。在选项卡下方的div元素中应用transition组件,在其内部定义动态组件,将数据对象中的current属性绑定到<component>元素的is属性。代码如下:
html
<div id="app">
<div class="box">
<ul class="munus" :class="current">
<li class="htmlcss" @click="current='htmlcss'">HTML5+CSS3</li>
<li class="JavaScript" @click="current='JavaScript'">JavaScript</li>
<li class="Java Web" @click="current='Java Web'">Java Web</li>
<li class="Android" @click="current='Android'">Android</li>
<li class="Java" @click="current='Java'">Java</li>
</ul>
<div class="right">
<div class="scroll">
<transition name="effect" mode="out-in">
<component :is="current"></component>
</transition>
</div>
</div>
</div>
</div>
- 编写CSS代码,为页面元素设置样式,通过在过渡类名中设置过渡属性使元素在显示和隐藏的切换过程中实现过渡效果。关键代码如下:
css
<style>
/*设置过度属性*/
.effect-enter-from, .effect-leave-to{
opacity: 0;
}
.effect-enter-active, .effect-leave-active{
transition:opacity .3s;
}
</style>
- 创建根组件实例,在实例中定义数据和组件,应用components选项注册5个局部组件,组件名称分别是htmlcss、JavaScript、Javaweb、Android和Java。代码如下:
html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
current: 'htmlcss'
}
},
components: {
htmlcss: {
template: `<div>
<div class="tab_right">
<img src="images/1.jpg">
</div>
</div>`
},
JavaScript: {
template: `<div>
<div class="tab_right">
<img src="images/2.jpg">
</div>
</div>`
},
JavaWeb: {
template: `<div>
<div class="tab_right">
<img src="images/3.jpg">
</div>
</div>`
},
Android: {
template: `<div>
<div class="tab_right">
<img src="images/4.jpg">
</div>
</div>`
},
Java: {
template: `<div>
<div class="tab_right">
<img src="images/3.jpg">
</div>
</div>`
}
},
methods: {
toggle: function(){
this.cName = this.cName === 'interest' ? 'sport' : 'interest';
}
}
});
vm.mount('#app');
</script>
运行实例,页面中有5个图书类别选项卡,单击不同的选项卡可以显示不同的图片,在内容发生变化时有一个过渡的效果。结果如图所示。

4、列表过度
实现列表过渡需要在<transition-group>组件中使用v-for指令,<transition-group>组件的特点如下:
- 与<transition>组件不同,它会以一个真实元素呈现,默认为一个<span>元素。通过设置tag属性可以将其更换为其他元素。
- 过渡模式不可用,因为不再相互切换特有的元素。
- 列表中的每个元素都需要提供唯一的key属性值。
例如,页面中有一个"插入头像"按钮和一个"移除头像"按钮,单击按钮可以向头像列表中插入或移除一个头像,在插入或移除时有一个过渡效果。关键代码如下:
html
<style>
.list-item {
display: inline-block;
margin-right: 15px;
}
/*插入过程和移除过程的过度效果*/
.effect-enter-active, .effect-leave-active{
transition: all 1s;
}
/* 开始插入、移除结束时的状态 */
.effect-enter-from, .effect-leave-to{
opacity: 0;
transform: translateY(30px);
}
</style>
<div id="app">
<div>
<button @click="add">插入头像</button>
<button @click="remove">移除头像</button>
<transition-group name="effect" tag="p">
<span v-for="item in items" :key="item" class="list-item">
<img :src="'images/' + item + '.jpg'">
</span>
</transition-group>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
const vm = Vue.createApp({
data() {
return {
items: [1, 2, 3, 4, 5]
}
},
methods: {
//生成随机数索引
ran: function(n) {
return Math.floor(Math.random() * n);
},
//在随机位置添加随机数
add: function() {
this.items.splice(this.ran(this.items.length), 0, this.ran(5) + 1);
},
//在随机位置移除随机数
remove: function() {
this.items.splice(this.ran(this.items.length), 1);
}
}
});
vm.mount('#app');
</script>


运行实例,当单击"插入头像"按钮时,会在下方的随机位置插入一个新的头像。当单击"移除头像"按钮时,会在下方的随机位置移除一个头像。