一、基本介绍
1,<transition>
组件
(1)如果某个元素或者组件需要使用过渡动画效果,只需使用 vue 提供的 <transition>
组件将其包裹起来封装成过渡组件。
(2)Vue 只有在插入,更新或者移除 DOM 元素时才会应用过渡效果,例如:
- v-if(条件渲染)
- v-show(条件展示)
- 动态组件
- 在组件的根节点上,并且被 vue 实例 DOM 方法触发。比如使用 appendTo 方法把组件添加到某个根节点上
2,过渡效果实现方式
过渡效果具体的实现方式分为如下几种:
- 在 CSS 过渡和动画中自动应用 class
- 可以配合使用第三方 CSS 动画库,如 Animate.css
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
3,动画执行逻辑
封装成过渡组件的元素被插入或者删除时,vue 将会做如下事情:
- 首先查找目标元素是否有 CSS 过渡或者动画,如果有就在适当的时候进行处理。
- 如果过渡组件设置了 JavaScript 钩子函数,vue 会在相应阶段调用钩子函数。
- 如果以上两者都没有,DOM 操作(插入或者删除)就在下一帧立即执行。
二、结合 CSS 实现动画效果
1,基本用法
(1)在组件过渡过程中,会有如下六个 CSS 类名进行切换:
-
v-enter
:进入过渡的开始状态,元素被插入时生效,只应用一帧后立刻删除。 -
v-enter-active
:进入过渡的结束状态,元素被插入时就生效,在过渡过程完成后移除。 -
v-enter-to
:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。(2.1.8 版加入) -
v-leave
:离开过渡的开始状态,元素被删除时触发,只应用一帧后立刻删除。 -
v-leave-active
:离开过渡的结束状态,元素被删除时生效,离开过渡完成后被删除。 -
v-leave-to
:定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。(2.1.8 版加入)
(2)下面是一个简单的样例:
- 通过点击按钮对下方文字进行显示或隐藏(交替)。
- 在显示或隐藏的过程中,会有淡入淡出的效果。
// HTML
<div id="app">
<button @click="show = !show">显示/隐藏</button>
<transition>
<div v-if="show">hello</div>
</transition>
</div>
// CSS
.v-enter{
opacity: 0;
}
.v-enter-to{
opacity: 1;
}
.v-enter-active{
transition: all 1s;
}
.v-leave{
opacity: 1;
}
.v-leave-to{
opacity: 0;
}
.v-leave-active{
transition: all 1s;
}
// JS
<script>
new Vue({
el: '#app',
data: {
show: true
}
});
</script>
注意
-
transition
中只能放 一个 元素, 多个元素无效
如果想给多个元素添加过渡动画, 那么就必须创建多个transition
组件
比如这里, 在一个transition组件放了两个div就会报错, 这个时候应该把两个div分别放在两个transition中
<transition>
<div v-if="show">hello</div>
<div v-if="show">hello</div>
</transition>
- 初始动画设置
默认情况下第一次进入的时候没没有动画的。
如果想一进来就有动画, 我们可以通过给transition
添加appear
属性的方式,
告诉Vue第一次进入就需要显示动画
<transition appear>
<div v-if="show">hello</div>
</transition>
- 给多个不同的元素指定不同的动画
如果有多个不同的元素需要执行不同的过渡动画,那么我们可以通过给transition
指定name
的方式
来指定"进入之前/进入之后/进入过程中, 离开之前/离开之后/离开过程中"对应的类名
来实现不同的元素执行不同的过渡动画
// HTML
<transition appear name="fade">
<div v-if="show">hello</div>
</transition>
// CSS
.fade-enter-active, .fade-leave-active{
transition: opacity 1s;
}
.fade-enter, .fade-leave-to{
opacity: 0;
}
2,自定义过渡类名
(1)前面的样例中六个过渡类名都是根据 transition 的 name 属性自动生成的,我们也可以通过 enter-class、enter-active-class、enter-to-class、leave-class、leave-active-class、leave-to-class 这六个属性来分别定义这六个类名。
<transition name="fade"
enter-class="fade-in-enter"
enter-active-class="fade-in-active"
enter-to-class="fade-in-end"
leave-class="fade-out-enter"
leave-active-class="fade-out-active"
leave-to-class="fade-out-end">
<div v-if="show">hello</div>
</transition>
(2)自定义过渡类名的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css
结合使用十分有用。
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" 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
}
})
3,通过 CSS 动画(animation)实现过渡效果
组件过渡效果不但可以通过 CSS 过渡实现,还可以通过 CSS 动画(关键帧动画)实现。
(1)效果
- 通过点击按钮让下方图片进行显示或隐藏(交替)。
- 在显示或隐藏的过程中,图片除了会有淡入淡出的效果,还会左右摇摆逐渐放大(或逐渐缩小)
(2)样例代码
// HTML
<template>
<div id="app">
<button @click="show = !show">显示/隐藏</button>
<br>
<transition name="fold">
<img v-show="show" src="./assets/logo.png">
</transition>
</div>
</template>
// JS
<script>
export default {
name: 'App',
data: function(){
return {
show: true
}
}
}
</script>
// CSS
<style>
.fold-enter-active {
animation-name: fold-in;
animation-duration: .5s;
}
.fold-leave-active {
animation-name: fold-out;
animation-duration: 5.5s;
}
@keyframes fold-in {
0% {
opacity: 0;
transform: scale(0.7) rotate(0deg);
}
33% {
opacity: 0.33;
transform: scale(0.8) rotate(5deg);
}
67% {
opacity: 0.67;
transform: scale(0.9) rotate(-5deg);
}
100% {
opacity: 1.0;
transform: scale(1.0) rotate(0deg);
}
}
@keyframes fold-out {
0% {
opacity: 1.0;
transform: scale(1.0) rotate(0deg);
}
33% {
opacity: 0.67;
transform: scale(0.9) rotate(-5deg);
}
67% {
opacity: 0.33;
transform: scale(0.8) rotate(5deg);
}
100% {
opacity: 0;
transform: scale(0.7) rotate(0deg);
}
}
</style>
4,显示地指定过渡持续时间
指定显性的过渡持续时间的作用:
(1)在很多情况下,Vue 会自动得出过渡效果的完成时机。默认情况下,Vue 会等待其在过渡效果的根元素的第一个 transitionend 或 animationend 事件。
(2)然而也可以不这样设定——比如,我们可以拥有一个精心编排的一系列过渡效果,其中一些嵌套的内部元素相比于过渡效果的根元素有延迟的或更长的过渡效果。
(1)我们可以用<transition>
组件上的 duration
属性定制一个显性的过渡持续时间 (以毫秒计):
<transition :duration="1000">...</transition>
(2)也可以分别指定进入和移出的持续时间:
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
</br>
三、JavaScript 钩子
我们先来看一个案例:
image在这个案例中, div在执行完最终动画后会直接跳回到原点.
可以发现通过
transition+类名
的方式确实能够实现过渡效果,但是实现的过渡效果并不能保存动画之后的状态。
因为Vue内部的实现是在过程中动态绑定类名, 过程完成之后删除类名
正是因为删除了类名, 所以不能保存最终的效果。想要解决这个问题, 就需要使用
JavaScript钩子
.
1,基本介绍
我们可以在 transition
属性中声明 JavaScript 钩子
,这些钩子函数可以结合 CSS transitions/animations
使用,也可以单独使用。
// HTML
<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>
// JS
methods: {
// 进入动画之前
beforeEnter: function (el) {
// ...
},
// 进入动画执行过程中
enter: function (el, done) {
// 当与 CSS 结合使用时, 回调函数 done 是可选的
done();
},
// 进入动画完成之后
afterEnter: function (el) {
// ...
},
// 进入动画被取消
enterCancelled: function (el) {
// ...
},
// 离开动画之前
beforeLeave: function (el) {
// ...
},
// 离开动画执行过程中
leave: function (el, done) {
// 当与 CSS 结合使用时, 回调函数 done 是可选的
done();
},
// 离开动画完成之后
afterLeave: function (el) {
// ...
},
// 离开动画被取消
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}
推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
注意:
- 在动画过程中必须写上
el.offsetWidth
或者el.offsetHeight
- 当只用 JavaScript 过渡的时候,在
enter
和leave
方法中必须调用done
方法, 否则after-enter
和after-leave
不会执行,它们将被同步调用,过渡会立即完成。- 需要需要添加初始动画, 那么需要把
done
方法包裹到setTimeout
方法中调用
现在我们就可以用JavaScript钩子来解决上面的案例中存在的问题:
// HTML
<div id="app">
<button @click="show = !show">显示/隐藏</button>
<transition appear
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
>
<div class="box" v-show="show"></div>
</transition>
</div>
// CSS
<style>
.box{
width: 300px;
height: 300px;
background: red;
}
</style>
// JS
<script>
new Vue({
el: '#app',
data: {
show: true
},
methods: {
// 进入动画开始之前
beforeEnter(el){
el.style.opacity = '0';
},
// 进入动画执行过程中
enter(el, done){
/*
注意: 如果是通过JS钩子来实现过渡动画,那么必须在动画执行过程中的回调函数中写上
el.offsetWidth / el.offsetHeight
* */
el.offsetWidth;
el.style.transition = 'all 3s';
// 注意: 动画执行完毕之后一定要调用done回调函数,否则后续的afterEnter钩子函数不会被执行
// done();
// 注意: 如果想让元素一进来就有动画, 那么最好延迟以下再调用done方法
setTimeout(function () {
done();
},0)
},
// 进入动画执行完毕之后
afterEnter(el){
el.style.opacity = '1';
el.style.marginLeft = '500px';
}
}
});
</script>
2,配合Velocity实现过渡动画
在Vue中我们除了可以自己实现过渡动画以外, 还可以结合第三方框架实现过渡动画
还是上面的例子, 但是通过Velocity.js来实现会简单很多
- 导入Velocity库
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
- 在动画执行过程钩子函数中编写Velocity动画
methods: {
beforeEnter(el){
},
enter(el, done){
Velocity(el, {opacity: 1, marginLeft: '500px'}, 3000);
done();
},
afterEnter(el){
}
}
</br>
四、多个元素、组件的过渡
1,多个元素的过渡
(1)对于原生标签我们可以使用 v-if/v-else
实现多元素过渡。下面是一个列表显示、数据空提示之间的过渡:
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>
(2)如果有两个以上的元素过渡可以这么写:
注意: 当有相同标签名的元素切换时,需要通过 key 属性设置唯一的值进行标记,从而让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容,就会导致我们的数据混乱。
<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>
(3)在一些场景中,也可以通过给同一个元素的 key
特性设置不同的状态来代替 v-if
和 v-else
。上面的例子可以重写为:
<transition>
<button v-bind:key="docState">
{{ buttonMessage }}
</button>
</transition>
// ...
computed: {
buttonMessage: function () {
switch (this.docState) {
case 'saved': return 'Edit'
case 'edited': return 'Save'
case 'editing': return 'Cancel'
}
}
}
2,过渡模式
过渡模式常常配合多个元素或者多个组件切换时使用,有如下两种模式:
-
in-out
:新元素先进行过渡,完成之后当前元素过渡离开。(默认为该模式) -
out-in
:当前元素先过渡离开,离开完成后新元素过渡进入。
<transition mode="out-in">
<button @click="show=!show" v-if="show" :key=1>on</button>
<button @click="show=!show" v-else :key=2>off</button>
</transition>
3,多个组件的过渡
多个组件的过渡简单很多 - 我们不需要使用 key attribute。相反,我们只需要使用动态组件:
<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>'
}
}
})
.component-fade-enter-active, .component-fade-leave-active {
transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active for below version 2.1.8 */ {
opacity: 0;
}
五、列表过渡
在此之前我们都是使用 <transition>
组件来实现过渡,其主要用于单个节点、或同一时间渲染多个节点中的一个这两种情况。而对于整个列表(比如使用 v-for)的过渡,则需要使用 <transition-group>
组件。
1,<transition-group>
说明
(1)不同于 <transition>
,<transition-group>
会以一个真实元素呈现:默认为一个 <span>
(我们可以通过 tag
属性更换为其他元素。)
(2)过渡模式不可用,因为我们不再相互切换特有的元素。
(3)<transition-group>
的内部元素总是需要提供唯一的 key
属性值。
(4)transition-group
和transition
的用法基本一致, 只是一个是给单个元素添加动画, 一个是给多个元素添加动画而已
2,列表的进入、离开过渡
(1)效果图
- 点击“插入一个元素”按钮,会在下方随机位置插入一个新的数字方块,新方块在插入过程中会有过渡动画。
- 点击“移除一个元素”按钮,会随机删除下方的一个数字方块,该方块在移除过程中会有过渡动画。
(2)样例代码
// HTML
<template>
<div id="app">
<div id="list-demo" class="demo">
<button v-on:click="add">插入一个元素</button>
<button v-on:click="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>
</div>
</template>
// CSS
<style>
/** 方块元素的样式 **/
.list-item {
display: inline-block;
margin-right: 10px;
background-color: orange;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
color: #ffffff;
}
/** 插入过程 **/
.list-enter-active{
transition: all 1s;
}
/** 移除过程 **/
.list-leave-active {
transition: all 1s;
}
/*** 开始插入、移除结束的位置变化 ***/
.list-enter, .list-leave-to {
opacity: 0;
transform: translateY(30px);
}
</style>
// JS
<script>
export default {
name: 'App',
data: function(){
return {
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)
},
}
}
</script>
3,列表的排序过渡
(1)上面的样例有个问题:虽然新插入的元素或者被移除的元素有动画效果,但它周围的元素会瞬间移动到他们新布局的位置,而不是平滑的过渡。要解决这个问题则需要借助新增的 v-move
特性。
v-move 特性会在元素改变定位的过程中应用,它像之前的类名一样:
- 可以通过
name
属性来自定义前缀(比如name="xxxx"
,那么对应的类名便是xxx-move
)- 也可以通过
move-class
属性手动设置自定义类名。
(2)这里对之前样例的 css
部分稍作修改,可以发现在插入或移出过程中,其它元素也会从原来的位置平滑过渡新的位置。
// CSS
<style>
/** 方块元素的样式 **/
.list-item {
display: inline-block;
margin-right: 10px;
background-color: orange;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
color: #ffffff;
}
/** 插入过程 **/
.list-enter-active{
transition: all 1s;
}
/** 移除过程 **/
.list-leave-active {
transition: all 1s;
position: absolute;
}
/*** 开始插入、移除结束的位置变化 ***/
.list-enter, .list-leave-to {
opacity: 0;
transform: translateY(30px);
}
/*** 元素定位改变时动画 ***/
.list-move {
transition: transform 1s;
}
</style>
(3)Vue
使用了一个叫 FLIP
简单的动画队列实现排序过渡。所以即使没有插入或删除元素,对于元素顺序的变化,也是支持过渡动画的。
// HTML
<template>
<div id="app">
<div id="list-demo" class="demo">
<button v-on:click="shuffle">乱序</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>
</div>
</template>
// CSS
<style>
/** 方块元素的样式 **/
.list-item {
display: inline-block;
margin-right: 10px;
background-color: orange;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
color: #ffffff;
}
/*** 元素定位改变时动画 ***/
.list-move {
transition: transform 1s;
}
</style>
// JS
<script>
export default {
name: 'App',
data: function(){
return {
items: [1,2,3,4,5,6,7,8,9]
}
},
methods: {
shuffle: function () {
return this.items.sort(function(a,b){ return Math.random()>.5 ? -1 : 1;})
}
}
}
</script>
(4)FLIP
动画不仅可以实现单列过渡,多维网格也同样可以过渡:
// HTML
<template>
<div id="app">
<div id="list-demo" class="demo">
<button v-on:click="shuffle">乱序</button>
<transition-group name="cell" tag="div" class="container">
<div v-for="cell in cells" :key="cell.id" class="cell">
{{ cell.number }}
</div>
</transition-group>
</div>
</div>
</template>
// CSS
<style>
.container {
display: flex;
flex-wrap: wrap;
width: 238px;
margin-top: 10px;
}
.cell {
display: flex;
justify-content: space-around;
align-items: center;
width: 25px;
height: 25px;
border: 1px solid #aaa;
margin-right: -1px;
margin-bottom: -1px;
}
.cell:nth-child(3n) {
margin-right: 0;
}
.cell:nth-child(27n) {
margin-bottom: 0;
}
.cell-move {
transition: transform 20s;
}
</style>
// JS
<script>
export default {
name: 'App',
data: function(){
return {
cells: Array.apply(null, { length: 81 })
.map(function (_, index) {
return {
id: index,
number: index % 9 + 1
}
})
}
},
methods: {
shuffle: function () {
this.cells.sort(function(a,b){ return Math.random()>.5 ? -1 : 1;})
}
}
}
</script>
(5)使用 js 钩子函数实现列表的交错过渡
我们也可以通过 data
属性与 JavaScript
通信,实现列表的交错过渡。
1,效果图
(1)在上方输入框中输入内容时,下方的列表会实时筛选并显示出包含该文字的条目。
(2)同时在列表条目的显示或者移出过程中,会有相应的过渡动画。
2,样例代码
、、 HTML
<template>
<div id="app">
<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>
</div>
</template>
// JS
<script>
import Velocity from 'velocity-animate'
export default {
name: 'App',
data: function(){
return {
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 , duration: 20000 }
)
}, delay)
},
leave: function (el, done) {
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(
el,
{ opacity: 0, height: 0 },
{ complete: done , duration: 20000 }
)
}, delay)
}
}
}
</script>
网友评论