1. CSS transition
在 Vue 的文档 单元素/组件的过渡
)中写到,Vue 提供了 transition
的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡。
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
举个例子,点击toggle,p 标签显示的 hello 就会淡化消失,再点击就会缓慢显现。
<!-- html -->
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
// JS
new Vue({
el: '#demo',
data: {
show: true
}
})
/* CSS */
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
过渡的类名
进入/离开 & 列表过渡 — Vue.js 中文文档 (bootcss.com)
在进入/离开的过渡中,会有6个class切换:
v-enter-active
、v-leave-active
定义进入、离开过渡(或动画)生效时的状态。,一般会合并写这2个类,因为动的都是一样的。
v-enter
、v-leave-to
表示进入和退出状态,这2个类也会合写。剩下2个一般不用。
p标签在进入动画的过程中经历了什么?
- 最开始 p标签 是隐藏起来的,并没有在DOM中出现。
- 当点击toggle按钮让它出现时,
v-enter
上的样式出现在动画的第一帧,并且附加上v-enter-active
。 - 之后便进入了动画/过渡的状态,
v-enter
被改为v-enter-to
。 - 动画/过渡状态结束之后,
v-enter-active
和v-enter-to
被一并删除。 - 同理,离出动画也是一样。
CSS过渡
CSS-过渡
常用的过渡都是使用 CSS 过渡。使用方法同上。
2. CSS animation
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend
事件触发时删除。
<!-- html -->
<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>
// JS
new Vue({
el: '#example-2',
data: {
show: true
}
})
/* CSS */
.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
(2.1.8+) leave-class
leave-active-class
-
leave-to-class
(2.1.8+)
他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。
比如在以下示例中修改enter-active-class
或leave-active-class
的属性可以直接实现想要的动画效果。
<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>
3. JS 操作动画
可以在 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>
// JS
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
进行回调,告诉他们这个阶段有事情要做。否则,它们将被同步调用,过渡会立即完成。
文档之中也有使用 Velocity.js
这个第三方js动画库的例子,这里不再赘述。
4. 多元素动画
对于原生标签可以使用 v-if/v-else
。当有相同标签名的元素切换时,需要通过 key
attribute 设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。为了能够实现动画效果,给在 <transition> 组件中的多个元素设置 key 是一个更好的实践。
<template>
<div id="example-4">
<transition name="fade">
<button key="on" v-if="status === 'off'" @click="status = 'on'">
ON
</button>
<button key="off" v-else @click="status = 'off'">OFF</button>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
status: "on",
};
},
};
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: all 1s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式
- in-out:新元素先进行过渡,完成之后当前元素过渡离开。
- out-in:当前元素先进行过渡,完成之后新元素过渡进入。
用 out-in 重写之前的开关按钮过渡:
<transition name="fade" mode="out-in">
再应用
// 先进再出
<transition name="fade" mode="in-out">
<style>
.fade-enter-active,.fade-leave-active {
transition: all 1s;
}
.fade-enter {
opacity: 0;
transform:translateX(100px)
}
.fade-leave-to {
opacity: 0;
transform:translateX(-100px)
}
#example-4{
position:relative;
}
button{
position:absolute;
}
</style>
// 实现轮播效果
<transition name="fade">
// css同上
多个组件的过渡
<transition name="component-fade" mode="out-in">
<button @click="view='v-a'">A</button>
<button @click="view='v-b'">B</button>
<component :is="view"></component>
</transition>
<script>
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>'
}
}
})
</script>
<style>
.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;
}
</style>
组件的过渡使用 <component :is="view"></component>
,要更加简单一些。
5. 列表动画
那么要同时渲染整个列表,就要使用 <transition-group>
组件,它的几个特点如下:
- 不同于
<transition>
,它会以一个真实元素呈现:默认为一个<span>
。可以通过tag
attribute 更换为其他元素。 - 过渡模式不可用,因为我们不再相互切换特有的元素。mode="out-in"不能用。
- 内部元素总是需要提供唯一的
key
attribute 值。 - CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
注意:不能加其它的标签,for循环模版外只能是<transition-group>,需要改变标签的话,可以使用tag
。
<template>
<div id="list-demo" class="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" :key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
</template>
<script>
export default {
name: "list-demo",
data() {
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>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active,
.list-leave-active {
transition: all 1s;
}
.list-enter,
.list-leave-to {
opacity: 0;
transform: translateY(30px);
}
</style>