离开/进入&列表过渡
概述
Vue在插入,更新或者移除DOM时,提供多种方式过渡效果:
- 在css过渡动画中自动应用class
- 配合第三方css动画库,animate.css
- 在过渡钩子函数中使用js直接操作DOM
- 配合第三方js动画库,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>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
当插入或删除包含在transition中的元素时,Vue会:
- 自动嗅探目标元素是否应用了css过渡动画,如果是,就在恰当的时机添加/删除css类名。
- 如果过渡组件提供了js钩子函数,就会在恰当时机调用。
- 如果没有js钩子也没有css过渡,DOM操作在下一帧立即执行。
过渡的类名
在过渡中,有6个class切换:
- v-enter:定义进入过渡的开始状态。在元素被插入之前生效,被插入之后的下一帧就移除。
- v-enter-active:定义进入过渡生效时的状态。
- v-enter-to:定义进入过渡的结束状态。
- v-leave:定义离开过渡的开始状态。
- v-leave-active:定义离开过渡生效时的状态。
- v-leave-to:定义离开过渡的结束状态。

如果transition没有名字,v-就是默认前缀。
CSS过渡
常用的过渡就是CSS过渡:
<div id="example-1">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.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 for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
CSS动画
与CSS过渡类似,区别是在动画中v-enter类名在节点插入DOM后不会立即删除,而是在animationend事件触发时才会删除。
<div id="example-2">
<button @click="show = !show">Toggle show</button>
<transition name="bounce">
<p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.</p>
</transition>
</div>
.bounce-enter-active {
animation: bounce-in .5s;
}
.bounce-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}

自定义过渡的类名
通过attribute改变类名:
- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class
同时使用过渡和动画
可以设置响应的事件监听器,监听transitionend和animationend,来让Vue知道过渡完成了。
如果既有过渡又有动画,可以使用type attribute并设置为animation或者transition来明确声明你要监听的类型。
显性的过渡持续时间
一般Vue可自动得到过渡效果的完成时机。在transition上duration属性定制一个显性的过渡持续事件
<transition :duration="1000">...</transition>
也可以定制进入和移出的时间:
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
JavaScript钩子
可以在属性中声明js钩子:
<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) {
// ...
}
}
当只用js过渡的时候,在enter,leave中必须使用done进行回调。否则会立刻完成。
与第三方库velocity结合的例子:
<!--
Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
-->
<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
el.style.transformOrigin = 'left'
},
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 attribute设置节点在初始渲染的过渡:
<transition appear>
<!-- ... -->
</transition>
多个元素的过渡
最常见的多标签过渡是一个列表和描述这个列表为空消息的元素:
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>
需要注意:当有相同标签名的元素切换时,需要通过key attribute设置唯一的值让Vue区分它们。
<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
也可以动态绑定key:
<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
使用v-if的多个元素过渡可以重写为绑定了动态属性的单个元素过渡:
<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>
过渡模式
有时候transition的默认行为——进入和离开同时发生,所以Vue提供了过渡模式:
- in-out:新元素先进行过渡,完成之后当前元素过渡离开。
- out-in:当前元素先进行过渡,完成之后新元素过渡进入。
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
in-out不常用。
多个组件的过渡
只需要使用动态组件:
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
.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;
}
列表过渡
如何同时渲染整个列表,比如使用v-for。
使用组件:
- 不同于transition,它会以一个真实元素呈现,默认是span,可通过tag attribute更换
- 过渡模式不可用,因为我们不再相互切换
- 需要提供唯一的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>
列表的排序过渡
还有一个特殊点,不仅可以进入和离开动画,还可以改变定位。
新增的v-move attribute会在元素改变定位的过程中应用。
<transition-group name="flip-list" tag="ul">
<li v-for="item in items" v-bind:key="item">
{{ item }}
</li>
</transition-group>
.flip-list-move {
transition: transform 1s;
}

其内部实现: Vue使用了一个叫FLIP简单的动画队列,使用transform将元素从之前的位置平滑过渡新的位置。
FLIP过渡的元素不能设置为display:inline.
FLIP动画不仅可以实现单列,也可用于网格。
列表的交错过渡
使用data属性和js通信,实现查询过渡:
<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的组件系统实现复用,要创建一个可复用的过渡组件,就把或者作为根组件即可。
动态过渡
最基本例子就是通过name attribute绑定动态值:
<transition v-bind:name="transitionName">
<!-- ... -->
</transition>
当你想用Vue的过渡系统来定义CSS切换会很有用。
所有过渡attribute都可以动态绑定,但还可以通过事件钩子获取上下文中所有数据,因为事件钩子都是方法。根据组件状态不同,js过渡会有不同表现:
<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>
创建动态过渡的最终方案是组件通过接受props来动态修改之前的过渡。
状态过渡
Vue的过渡系统对于数据元素本身的动效:
- 数字的运算
- 颜色显式
- SVG节点
- 元素大小和其他属性
这些数据用数值结合响应式和组件实现过渡。
状态动画与侦听器
通过侦听器我们可以监听到任何数值属性的更新,使用GreenSock:
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<div id="animated-number-demo">
<input v-model.number="number" type="number" step="20">
<p>{{ animatedNumber }}</p>
</div>
new Vue({
el: '#animated-number-demo',
data: {
number: 0,
tweenedNumber: 0
},
computed: {
animatedNumber: function() {
return this.tweenedNumber.toFixed(0);
}
},
watch: {
number: function(newValue) {
TweenLite.to(this.$data, 0.5, { tweenedNumber: newValue });
}
}
})

<script src="https://cdn.jsdelivr.net/npm/tween.js@16.3.4"></script>
<script src="https://cdn.jsdelivr.net/npm/color-js@1.0.3"></script>
<div id="example-7">
<input
v-model="colorQuery"
v-on:keyup.enter="updateColor"
placeholder="Enter a color"
>
<button v-on:click="updateColor">Update</button>
<p>Preview:</p>
<span
v-bind:style="{ backgroundColor: tweenedCSSColor }"
class="example-7-color-preview"
></span>
<p>{{ tweenedCSSColor }}</p>
</div>
var Color = net.brehaut.Color
new Vue({
el: '#example-7',
data: {
colorQuery: '',
color: {
red: 0,
green: 0,
blue: 0,
alpha: 1
},
tweenedColor: {}
},
created: function () {
this.tweenedColor = Object.assign({}, this.color)
},
watch: {
color: function () {
function animate () {
if (TWEEN.update()) {
requestAnimationFrame(animate)
}
}
new TWEEN.Tween(this.tweenedColor)
.to(this.color, 750)
.start()
animate()
}
},
computed: {
tweenedCSSColor: function () {
return new Color({
red: this.tweenedColor.red,
green: this.tweenedColor.green,
blue: this.tweenedColor.blue,
alpha: this.tweenedColor.alpha
}).toCSS()
}
},
methods: {
updateColor: function () {
this.color = new Color(this.colorQuery).toRGB()
this.colorQuery = ''
}
}
})
.example-7-color-preview {
display: inline-block;
width: 50px;
height: 50px;
}

动态状态过渡
把过渡放到组件里
管理太多的状态过渡会增加复杂性
赋予设计以生命
使用svg,发挥想象,让其动起来
