进入、离开和列表的过渡
当从 DOM 中插入、更新或移除项目时,Vue 提供多种应用过渡效果的方式。包括以下工具:
- 在 CSS 过渡和动画中自动处理 class
- 可以配合使用第三方 CSS 动画库,如 Animate.css
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
Vue 提供了 transition
外层包裹容器组件(wrapper component),可以给下列情形中的任何元素和组件添加进入/离开(enter/leave)过渡
- 条件渲染(使用
v-if
) - 条件展示(使用
v-show
) - 动态组件
- 组件根节点
//这是一个简单的示例new Vue({ el: '#demo', data: { show: true }}).fade-enter-active, .fade-leave-active { //设定进入和离开过程中的效果,改变属性opacity和时间0.5s transition: opacity .5s;}.fade-enter, .fade-leave-to /* .fade-leave-active 在低于版本 2.1.8 中 */ { //设定进入前和离开后的效果 opacity: 0;}hello
//通过show的值判断是否展示该元素
-
v-enter
:进入式过渡(entering transition)的开始状态。在插入元素之前添加,在插入元素之后一帧移除。 -
v-enter-active
:进入式过渡的激活状态。应用于整个进入式过渡时期。在插入元素之前添加,过渡/动画(transition/animation)完成之后移除。此 class 可用于定义进入式过渡的 duration, delay 和 easing 曲线。 -
v-enter-to
:仅适用于版本 2.1.8+。进入式过渡的结束状态。在插入元素之后一帧添加(同时,移除v-enter
),在过渡/动画完成之后移除。 -
v-leave
:离开式过渡(leaving transition)的开始状态。在触发离开式过渡时立即添加,在一帧之后移除。 -
v-leave-active
:离开式过渡的激活状态。应用于整个离开式过渡时期。在触发离开式过渡时立即添加,在过渡/动画(transition/animation)完成之后移除。此 class 可用于定义离开式过渡的 duration, delay 和 easing 曲线。 -
v-leave-to
:仅适用于版本 2.1.8+。离开式过渡的结束状态。在触发离开式过渡之后一帧添加(同时,移除v-leave
),在过渡/动画完成之后移除。
new Vue({ el: '#example-1', data: { show: true }})/* 进入和离开动画可以分别 *//* 设置不同的持续时间(duration)和动画函数(timing function) */.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-to //进入开始和离开结束的状态/* .slide-fade-leave-active 在低于 2.1.8 版本中 */ { transform: translateX(10px); opacity: 0;}hello
new Vue({ el: '#example-2', data: { show: true }}).bounce-enter-active { animation: bounce-in .5s; //设定进入过程中执行animation动画bounce-in,时间0.5s}.bounce-leave-active { //设定离开过程中逆向(reverse)执行animation: bounce-in 时间0.5s animation: bounce-in .5s reverse;}@keyframes bounce-in { 0% { transform: scale(0); //0%时期状态 规模0 } 50% { transform: scale(1.5); //50%时期状态 规模1.5 } 100% { transform: scale(1); //同上 }}Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.
你也可以通过提供一下属性来指定自定义过渡类名
enter-class
enter-active-class
enter-to-class
(2.1.8+)leave-class
leave-active-class
leave-to-class
(2.1.8+)
它们将覆盖默认约定的类名,这对于将 Vue 的过渡系统和其他现有的第三方 CSS 动画库(如 )集成使用会非常有用。
//引用animate.cssnew Vue({ el: '#example-3', data: { show: true }})hello
(不懂,如果使用第三方,类名覆盖了还怎么过度?)
Vue 为了知道过渡何时完成,必须附加相应的事件监听器。它可以是 transitionend
或 animationend
,这取决于给元素应用的 CSS 规则。如果你使用其中任何一种,Vue 能自动识别正确的类型并设置相应的事件监听器。
但是,在一些情况下,你可能需要给同一个元素同时设置过渡和动画,比如由 Vue 触发 CSS 动画,同时在鼠标悬停时触发 CSS 过渡。在这种情况下,你可能需要通过 type
属性,来显式声明需要 Vue 监听的类型,值可以是 animation
或 transition
。
你可以使用 <transition>
组件上的 duration
属性 ,来指定一个显式的过渡持续时间(以毫秒为单位):
... //过度持续时间... //进入和离开分别的持续时间
可以在属性中声明 JavaScript 钩子
// ...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 过渡/动画使用,也可以单独使用。
当仅使用 JavaScript 式过渡的时候, 在 enter
和 leave
钩子函数中,必须有 done
回调函数。否则,这两个钩子函数会被同步调用,过渡会立即完成。
推荐对于仅使用 JavaScript 的过渡显式添加 v-bind:css="false"
,以便 Vue 可以跳过 CSS 侦测。这也可以防止 CSS 规则意外干涉到过渡。
现在我们深入来看一个示例。这里是一个使用 Velocity.js 的 JavaScript 式过渡:
//引用velocity.jsDemo
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 }) } }})
如果你还想在节点初始渲染时应用过渡,可以添加 appear
属性:
默认情况下,对于进入和离开,会使用特定过渡。但是,如果你有需要,也可以指定自定义 CSS 类名:
= 2.1.8 支持) appear-active-class="custom-appear-active-class">
以及指定自定义 JavaScript 钩子函数:
我们将在下面讨论,但是还是可以使用 v-if
/v-else
,来对初始元素之间进行切换过渡。最常见的是,一个列表容器和描述列表为空的消息,这两个元素间的切换过渡:
Sorry, no items found.
当在具有相同标签名称的元素之间切换时,需要通过给它们分配唯一的 key
属性,以使 Vue 感知它们是不同的元素。否则 Vue 的编译器将因为效率,只会替换元素内部的内容。即使在技术上没有必要,但是,给 <transition>
组件中的多个元素设置 key,被认为是一个最佳实践。
通过给同一元素的 key
属性,设置不同的状态来进行过渡。而无需使用 v-if
和 v-else。
实际上,使用 v-if
的多个元素之间的过渡,还可以改为在单个元素上绑定动态属性的方式,来在任意数量的元素之间进行转换。
可以重写为:
// ...computed: { buttonMessage: function () { switch (this.docState) { case 'saved': return 'Edit' case 'edited': return 'Save' case 'editing': return 'Cancel' } }}
当一个过渡进入时,另一个过渡离开。这是 <transition>
的默认行为 - 进入和离开同时发生。
同时生效的进入式和离开式过渡不能满足所有要求,所以 Vue 提供了可选的过渡模式:
-
in-out
:新元素先过渡进入(transition in),过渡完成之后,当前元素过渡离开(transition out)。 -
out-in
:当前元素先过渡离开(transition out),过渡完成之后,新元素过渡进入(transition in)。
多个组件之间的过渡甚至更简单 - 我们不需要使用 key
属性。相反,我们需要使用:
new Vue({ el: '#transition-components-demo', data: { view: 'v-a' }, components: { 'v-a': { template: ' Component A' }, 'v-b': { template: 'Component B' } }}) .component-fade-enter-active, .component-fade-leave-active { transition: opacity .3s ease;}.component-fade-enter, .component-fade-leave-to/* .component-fade-leave-active 在低于 2.1.8 版本中 */ { opacity: 0;}
当我们整个列表的每一项(例如使用 v-for
)都需要同时进行渲染呢?在这种情况下,我们将使用 <transition-group>
组件。在我们深入示例之前,先来了解关于这个组件的一些要点:
- 不同于
<transition>
,它会以一个真实元素渲染:默认为<span>
。你也可以通过tag
属性更换为其他渲染元素 - 它内部的元素必须具有唯一的
key
属性
现在让我们来深入一个示例,进入式过渡和离开式过渡都使用与之前相同的 CSS 类名:
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) //选取一个随机索引(0.x和当前数组长度乘) }, add: function () { this.items.splice(this.randomIndex(), 0, this.nextNum++) //num++然后加入数组中随机索引位置 }, 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-to /* .list-leave-active 在低于 2.1.8 版本中 */ { opacity: 0; transform: translateY(30px);}//列表渲染过度用transition-group标签 { { item }}
<transition-group>
组件还有一个暗藏玄机之处。不仅可以在进入和离开时进行动画,还可以在位置改变时进行动画。使用此功能所需要知道的唯一新的概念是,在项目位置改变时添加额外的 v-move
类名。与其他类名相同,它的前缀也和设置的 name
属性的值相匹配,你也可以通过 move-class
属性来手动指定类名。
此类名对于指定过渡时间和 easing 过渡曲线非常有用,如下所示:
//引用lodash.jsnew Vue({ el: '#flip-list-demo', data: { items: [1,2,3,4,5,6,7,8,9] }, methods: { shuffle: function () { this.items = _.shuffle(this.items) //调用loadsh.js中的_.shuffle } }}).flip-list-move { //列表过度,项目发生改变时会添加额外的v-modle属性,通过设置此属性就可以实现变动的项目的过渡效果 transition: transform 1s;}//此标签真实渲染,通过tag设定为ul,下面可用li,不设定默认span标签 //‘一定带唯一的key { { item }}
需要注意的是,使用 FLIP 过渡的元素,在设置为 display: inline
时,无法正常运行。作为替代方案,可以将元素设置为 display: inline-block
,或者将元素放置于 flex 上下文(flex context)中。
FLIP 动画不局限于单个轴线方向(single axis),多个维度网格(multidimensional grid)也:
通过 data 属性与 JavaScript 式过渡的通信,就可以实现列表的逐项渐进过渡:
//引用velocity.js//和data中的query对象实现双向绑定,query实时接受input的valuenew 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 //字母小写=》匹配query ??!==-1 }) } }, methods: { beforeEnter: function (el) { el.style.opacity = 0 el.style.height = 0 }, enter: function (el, done) { var delay = el.dataset.index * 150 //dataset可以读取自定义数据属性,这里dataset.index读取data-index setTimeout(function () { //设定延迟执行时间,这样data-index越大的过度延迟越久,视觉上就实现了逐步渲染 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) } }}){ { item.msg }}
通过 Vue 的组件系统可以实现复用过渡。要创建一个可复用过渡,你需要做的就是将 <transition>
或者 <transition-group>
作为组件根节点,然后将全部子内容放置在 transition 组件中就可以了。
Vue.component('my-special-transition', { template: '\\ \ \ ', methods: { beforeEnter: function (el) { // ... }, afterEnter: function (el) { // ... } }})
函数组件更适合完成这个任务:
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) }})
其实,在 Vue 中即使是过渡也是由数据驱动的!动态过渡最基本的例子是,将 name
属性(attribute)和动态属性(dynamic property)绑定在一起。
当你使用多个 Vue 过渡类名约定,来定义 CSS 过渡/动画,并在不同的类名约定之间切换时,动态过渡会非常有用。
所有的过渡属性都可以动态绑定。并且不仅是属性,由于事件钩子函数都是 Vue 的方法(methods),所以可以从 this 上下文访问到所有数据。这意味着,根据组件的状态,JavaScript 式过渡的表现可能会有所不同。
Fade In: //动态绑定max属性到data,但是有何意义? Fade Out:hello
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 } } ) } }})