进入/离开 & 列表过渡
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:
- 在 CSS 过渡和动画中自动应用 class
- 可以配合使用第三方 CSS 动画库,如 Animate.css
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
这里,只讲进入、离开和列表的过渡
单元素组件过渡
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用
v-if) - 条件展示 (使用
v-show) - 动态组件
- 组件根节点
过渡的类名 & CSS过渡
常用的过渡都是使用 CSS 过渡,插入或删除包含在 transition 标签中的元素时,会对这个元素添加和移除 进入/离开 的6个class类名来完成动画
v-enter:定义元素进入的初始状态v-enter-active:定义元素进入过渡动画的transitionv-enter-to:定义元素进入动画结束时的状态v-leave:定义元素离开的初始状态v-leave-active:定义元素离开过渡动画的transitionv-leave-to:定义元素离开动画结束时的状态
- 上方提供了四个时间点元素的状态,如果元素的过渡是
从哪儿来的回哪儿去,进入前状态和离开后状态相同,进入后状态和离开前状态相同,那么css可以写在一起
.v-enter,.v-leave-to{ //元素进入前和离开结束时的状态
//...
}
.v-enter-to,.v-leave{ //这组css会在过渡结束后被移除,立即恢复,没有过渡动画
//... //所以为了保证有过渡,元素本身也需要有transition才能有过渡动画来恢复初始样式
}
v-enter-active和v-leave-active用来定义进入和离开动画的transition,同样的也是可以写到一起的
.v-enter-active,.v-leave-active{
//...
}
使用 <transition> 标签来包裹需要过渡的元素,v- 是这些类名的默认前缀。标签内写入name属性并命名 <transition name="my-transition">,那么 v-enter 要写成 my-transition-enter ,来为这类元素单独定制过渡动画
CSS动画
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名不会立即删除,而是在动画结束时删除
<div>
<button @click="show = !show">Toggle show</button>
<transition>
<p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</transition>
</div>
new Vue({
data: {
show: true
}
})
.v-enter-active {
animation: bounce-in .5s;
}
.v-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
自定义过渡类名
enter-classenter-active-classenter-to-classleave-classleave-active-classleave-to-class
他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<div>
<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({
data: {
show: true
}
})
同时使用过渡 & 动画
Vue 提供了 transitionend 和 animationend 的事件监听器,Vue 能自动识别类型并设置监听
但是,在一些场景中,需要给同一个元素同时设置两种过渡动效,比如 animation 很快的被触发并完成了,而 transition 效果还没结束。在这种情况中,你就需要使用 type 属性并设置 animation 或 transition 来明确声明你需要 Vue 监听的类型
显性的过渡持续时间
可以用 <transition> 组件上的 duration prop 定制一个显性的过渡持续时间 (以毫秒计):
<transition :duration="1000">...</transition>
也可以定制进入和移出的持续时间:
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
JavaScript钩子
可以在 attribute 中声明 JavaScript 钩子
<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>
// ...
methods: {
// --------
// 进入中
// --------
beforeEnter: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},
// --------
// 离开时
// --------
beforeLeave: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}
这些钩子函数可以结合 CSS transitions/animations 使用,也可以单独使用
当只用 JavaScript 过渡的时候,在
enter和leave中必须使用done进行回调,否则,它们将被同步调用,过渡会立即完成推荐对于仅使用 JavaScript 过渡的元素添加
v-bind:css="false",Vue 会跳过 CSS 的检测,这也可以避免过渡过程中 CSS 的影响
多个元素的过渡
单个节点,同时间渲染多个节点中的一个,对于原生标签可以使用 v-if/v-else,最常见的多标签过渡是一个列表和描述这个列表为空消息的元素:
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>
可以这样使用,但是有一点需要注意:
当有相同标签名的元素切换时,需要通过 key 属性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容,即使在技术上没有必要,给在 <transition> 组件中的多个元素设置 key 是一个更好的实践
示例:
<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
在一些场景中,也可以通过给同一个元素的 key attribute 设置不同的状态来代替 v-if 和 v-else,上面的例子可以重写为:
<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
使用多个 v-if 的多个元素的过渡可以重写为绑定了动态 property 的单个元素过渡。例如:
<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>
可以重写为:
<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'
}
}
}
过渡模式
多个元素之间切换,进入和离开同时发生,是 <transition> 的默认行为,Vue 提供了过渡模式
out-in:当前元素离开后,新元素再进入
in-out:新元素先进入,完成后当前元素离开,这个模式不常用,只是提供了一个可能
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
多个组件过渡
多个组件的过渡简单很多 - 我们不需要使用 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{
opacity: 0;
}
列表过渡
同时渲染整个列表,比如使用 v-for 在这种场景中,使用 <transition-group> 组件,关于这个组件的几个特点:
- 不同于
<transition>,它会以一个真实元素呈现:默认为一个<span>,也可以通过tag属性更换为其他元素 - 过渡模式不可用,因为我们不再相互切换特有的元素
- 内部元素总是需要提供唯一的
key属性值 - CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身
列表的进入/离开过渡
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,而不是平滑的过渡,下面会解决这个问题
列表的排序过渡
<transition-group> 组件还有一个特殊之处,不仅可以进入和离开动画,还可以改变定位, v-move class会在元素的改变定位的过程中应用,使用 transforms 将元素从之前的位置平滑过渡新的位置,像之前的类名一样,可以通过 name 属性的值来自定义前缀,也可以通过 move-class 属性手动设置
元素不能设置为
display: inline,作为替代方案,可以设置为display: inline-block或者放置于 flex 中,v-move下面要给v-leave-active加上absolute定位,来解决移除后元素没有动画的问题,如果因为absolute定位导致元素宽高异常再看情况解决
.v-move {
transition: 0.3s ease-in-out;
}
.v-leave-active {
position: absolute;
height: 100%;
}
可复用的过渡
过渡可以通过 Vue 的组件系统实现复用,要创建一个可复用过渡组件,就是将 <transition> 或者 <transition-group> 作为根组件,然后将任何子组件放置在其中就可以了
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) {
// ...
}
}
})
动态过渡
在 Vue 中即使是过渡也是数据驱动的,动态过渡最基本的例子是通过 name attribute 来绑定动态值
<transition v-bind:name="transitionName">
<!-- ... -->
</transition>
当你想用 Vue 的过渡系统来定义的 CSS 过渡/动画在不同过渡间切换会非常有用
所有过渡 attribute 都可以动态绑定,但不仅仅只有 attribute 可以利用,还可以通过事件钩子获取上下文中的所有数据,因为事件钩子都是方法,这意味着,根据组件的状态不同, JavaScript 过渡会有不同的表现
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<div id="dynamic-fade-demo" class="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-if="stop"
v-on:click="stop = false; show = false"
>Start animating</button>
<button
v-else
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: true
},
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
}
}
)
}
}
})
最后,创建动态过渡的最终方案是组件通过接受 props 来动态修改之前的过渡。一句老话,唯一的限制是你的想象力。