12. Enter/Leave & List Transitions(进入/离开&转换列表)
进入/离开 和 列表转换
概观
当插入,更新或从DOM中删除项目时,Vue提供了多种应用转换效果的方法。这包括以下工具:
- 自动为CSS转换和动画应用类
- 集成第三方CSS动画库,如Animate.css
- 使用JavaScript在转换挂钩期间直接操作DOM
- 整合第三方JavaScript动画库,例如Velocity.js
在这个页面上,我们只会介绍进入,离开和列表转换,但您可以看到管理状态转换的下一部分。
过渡单个元素/组件
Vue提供了一个transition
包装组件,允许您在以下上下文中为任何元素或组件添加进入/离开转换:
- 有条件呈现(使用
v-if
)
- 有条件显示(使用
v-show
)
- 动态组件
- 组件根节点
这就是一个例子:
<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-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0
}
切换
hello
transition
插入或移除组件中包含的元素时,会发生以下情况:
1. Vue会自动嗅探目标元素是否有CSS转换或动画应用。如果是这样,CSS转换类将在适当的时机添加/删除。
2. 如果转换组件提供了JavaScript钩子,这些钩子将在适当的时机调用。
3. 如果没有检测到CSS转换/动画,并且没有提供JavaScript钩子,插入和/或删除的DOM操作将在下一帧立即执行(注意:这是一个浏览器动画框架,与Vue的概念不同nextTick
).Transition ClassesThere有六类申请进入/离开转换。
4. v-enter
:输入的起始状态。插入元素之前添加,插入元素后删除一帧。
5. v-enter-active
:输入的活动状态。在整个进入阶段应用。插入元素之前添加,当过渡/动画完成时删除。该类可用于定义输入转换的持续时间,延迟和缓动曲线。
6. v-enter-to
:仅适用于版本2.1.8+。
结束进入状态。插入元素后添加一个框架(同时v-enter
删除),当转换/动画完成时删除。
7. v-leave
:开始状态请假。当离开转换被触发时立即添加,在一帧后删除。
8. v-leave-active
:离开的活动状态。在整个离开阶段应用。当离开转换被触发时立即添加,当转换/动画结束时删除。该类可用于定义离开转换的持续时间,延迟和缓动曲线。
9. v-leave-to
:仅适用于版本2.1.8+。
结束离开状态。加入一个帧一个离开转换被触发(在相同的时间之后v-leave
被移除),当过渡/动画完成除去。
10. enter-class
11. enter-active-class
12. enter-to-class
(2.1.8+)
13. leave-class
14. leave-active-class
15. leave-to-class
(2.1.8+)
这些将覆盖传统的类名称。当您想要将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
}
})
一起使用转换和动画
Vue需要附加事件监听器才能知道转换何时结束。它可以是transitionend
或者animationend
,取决于所应用的CSS规则的类型。如果您只使用其中一种,Vue可以自动检测正确的类型。
然而,在某些情况下,您可能希望同时拥有两个元素,例如由Vue触发的CSS动画,以及悬停时的CSS过渡效果。在这些情况下,您必须在type
属性中明确声明您想要Vue关心的类型,其值为animation
或transition
。
显式过渡时间
New in 2.2.0+
在大多数情况下,Vue可以自动计算转换完成的时间。默认情况下,Vue等待根转换元素上的第一个transitionend
或animationend
事件。然而,这可能并不总是需要的 - 例如,我们可能有一个编排的转换序列,其中一些嵌套的内部元素具有比根转换元素延迟的转换或更长的转换持续时间。
在这种情况下,您可以使用组件duration上的prop 指定明确的转换持续时间(以毫秒为单位)<transition>:
<transition :duration="1000">...</transition>
您还可以为输入和保留持续时间指定单独的值:
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
JavaScript Hooks
你也可以在属性中定义JavaScript hooks:
<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: {
// --------
// ENTERING
// --------
beforeEnter: function (el) {
// ...
},
// the done callback is optional when
// used in combination with CSS
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},
// --------
// LEAVING
// --------
beforeLeave: function (el) {
// ...
},
// the done callback is optional when
// used in combination with CSS
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled only available with v-show
leaveCancelled: function (el) {
// ...
}
}
这些钩子可以与CSS转换/动画结合使用或单独使用。
当使用JavaScript的过渡只,将
需要为回调和钩
。否则,它们将
被同步调用,并且转换将
立即结束。done
enter
leave
明确添加v-bind:css="false"
仅用于JavaScript的转换也是一个好主意,以便Vue可以跳过CSS检测。这也可以防止CSS规则意外干扰转换。
现在我们来看一个例子。这里使用了Velocity.js的JavaScript转换:
<!--
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"></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 })
}
}
})
初始渲染过渡
如果您还想在节点的初始渲染上应用转场,则可以添加appear
属性:
<transition appear>
<!-- ... -->
</transition>
默认情况下,这将使用为进入和离开指定的转换。但是,如果您想要,也可以指定自定义CSS类:
<transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class" (2.1.8+)
appear-active-class="custom-appear-active-class"
>
<!-- ... -->
</transition>
和自定义JavaScript钩:
<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook"
>
<!-- ... -->
</transition>
元素之间的过渡
我们稍后讨论组件之间的转换,但也可以使用v-if
/ 在各个原始元素之间转换v-else
。最常见的两元素转换之一是在列表容器和描述空列表的消息之间:
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>
这很有效,但有一点需要注意:
在具有相同标签名称的元素之间切换时,必须通过为Vue指定唯一key属性来区分它们是不同的元素。否则,Vue的编译器只会替换元素的内容以提高效率。即使在技术上没有必要,但总是在<transition>组件内键入多个项目被认为是很好的做法。
例如:
<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
在这些情况下,您也可以使用该key
属性在同一元素的不同状态之间转换。代替使用的v-if
和v-else
,上述例子可以被重写为:
<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
实际上可以在任意数量的元素之间进行转换,可以使用多个v-if
s或将单个元素绑定到动态属性。例如:
<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提供了一些替代转换模式
:
in-out
:首先新元素转换,然后完成时,当前元素转换出来。
out-in
:当前元素首先转换出来,然后完成后,新元素转入。
现在让我们用out-in
更新我们开/关按钮的转换:
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
除了一个属性外,我们已经修复了原始转换,而无需添加任何特殊样式。
该in-out
模式并不经常使用,但有时对于稍微不同的过渡效果有用。让我们尝试将它与早先我们所做的幻灯片淡化转换相结合:
(这种方法)很酷,对吧?
组件之间的转换
组件之间的转换甚至更简单 - 我们甚至不需要该key
属性。相反,我们包装一个动态组件:
<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 below version 2.1.8 */ {
opacity: 0;
}
列表转换
到目前为止,我们已经为以下方面管理过渡:
- 个别节点
- 多个节点一次仅渲染1个节点
那么当我们有一整套我们想要同时渲染的物品时,例如v-for。在这种情况下,我们将使用该<transition-group>组件。在我们深入研究一个例子之前,有几件重要的事情要了解这个组件:
- 与<transition>不同的是,它呈现一个实际的元素:a <span>默认情况下。您可以更改使用该tag属性呈现的元素。
- 里面的元素
始终需要
具有唯一的key
属性
列表输入/离开转场
现在我们来看一个例子,使用我们以前使用过的相同的CSS类来转换进入和离开:
<div id="list-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-to /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px
}
这个例子有一个问题。当你添加或删除一件物品时,它周围的物品会立即进入新位置,而不是平稳过渡。我们稍后会解决这个问题。
列表移动转换
<transition-group>组件还有另一个窍门。它不仅可以动画进入和离开,而且可以改变位置。您需要知道的使用此功能的唯一新概念是添加了v-move类,该类在项目更改位置时添加。像其他类一样,它的前缀将与提供的属性的值匹配,您也可以手动指定具有namemove-class属性的类。
该类对于指定转换时间和缓动曲线非常有用,如下所示:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></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;
}
这可能看起来很神奇,但在引擎盖下,Vue正在使用名为FLIP的动画技术,以使用变换平滑地将元素从旧位置过渡到新位置。
我们可以将此技术与我们之前的实现结合起来,为每个可能的变化添加动画。
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></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-to
/* .list-complete-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px
}
.list-complete-leave-active {
position: absolute;
}
一个重要的提示是,这些FLIP转换不适用于设置为的元素display: inline
。作为替代,您可以display: inline-block
在Flex上下文中使用或放置元素。
这些FLIP动画也不限于单个轴。多维网格中的项目也可以进行转换:
交错列表转换
通过通过数据属性与JavaScript转换进行通信,也可以在列表中错开转换:
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></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)
}
}
})
可重复使用的转换
转换可以通过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.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
属性绑定到动态属性。
<transition v-bind:name="transitionName">
<!-- ... -->
</transition>
当您使用Vue的过渡类约定定义CSS过渡/动画并希望在它们之间切换时,这会很有用。
实际上,任何转换属性都可以动态绑定。这不仅是属性。由于事件挂钩是方法,因此它们可以访问上下文中的任何数据。这意味着取决于组件的状态,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
}
}
)
}
}
})
最后,创建动态转换的最终方式是通过接受道具来改变要使用的转换的性质的组件。这听起来很俗气,但唯一的限制是你的想象力。