简介
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡:
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
过渡类名
在进入的过渡中,会有 3 个 class 切换。
v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
v-enter-to:进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
离开的过渡中,会有 3 个 class 切换。
v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
单元素/组件的过渡
进入/离开效果相同的css过渡
以下便是使用transition包裹p标签实现p标签淡入淡出的例子。这里的transition使用了name=‘xxx’字段,所以默认的.v-enter被替换成了.xxx-enter,其余类名同理。
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/vue.js"></script>
<style type="text/css">
.xxx-enter-active,
.xxx-leave-active {
/* 进入与离去的过程中opacity改变过程持续.5s */
transition: opacity .5s;
}
.xxx-enter,
.xxx-leave-to {
/* 进入之前/离开之后opacity为0(进入之后,离开之前样式是既定的,不用定义) */
opacity: 0;
}
</style>
</head>
<body>
<div id="content">
<button @click="isShow=!isShow">执行动画</button>
<transition name="xxx">
<p v-if="isShow">我出现了</p>
</transition>
</div>
</body>
<script>
var vm = new Vue({
el: '#content',
data: {
isShow: true
}
});
</script>
</html>
运行:
进入/离开效果不同的css过渡
<style type="text/css">
/* 进入时所有效果匀速播放.5s */
.xxx-enter-active {
transition: all .5s ease;
}
/* 离开时所有效果变速播放1s */
.xxx-leave-active {
transition: all 1s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.xxx-enter,
.xxx-leave-to {
/* 两个效果,transition后面用all */
transform: translateX(10px);
opacity: 0;
}
</style>
运行:
css动画
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。
<style type="text/css">
.xxx-enter-active {
/* 使用xxx-in这一变化 */
animation: xxx-in .5s;
}
.xxx-leave-active {
/* 使用xxx-in这一变化的反转 */
animation: xxx-in .5s reverse;
}
/* 按照执行进度设置元素状态 */
@keyframes xxx-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
</style>
<body>
<div id="content">
<button @click="isShow=!isShow">执行动画</button>
<transition name="xxx">
<p v-if="isShow">我出现了11111111111111111111111111111111111111111111111</p>
</transition>
</div>
</body>
运行,效果正常:
但是如果内容较短:
<p v-if="isShow">我出现了</p>
运行效果会不正常:
设置样式display: inline-block可以解决此问题:
<p v-if="isShow" style="display: inline-block;">我出现了</p>
运行:
自定义过渡类名+第三方动画库
我们可以通过以下 attribute 来自定义过渡类名,他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用既方便又炫酷:
- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/vue.js"></script>
<!-- 引入第三方动画库Animate.css -->
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<style type="text/css">
</style>
</head>
<body>
<div id="content">
<button @click="isShow=!isShow">执行动画</button>
<!-- 使用自定义过渡的类名enter-active-class、leave-active-class -->
<transition
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
name="xxx">
<p v-if="isShow" >我出现了</p>
</transition>
</div>
</body>
<script>
var vm = new Vue({
el: '#content',
data: {
isShow: true
}
});
</script>
</html>
运行:
多个元素的过渡
当有相同标签名的元素切换时,需要通过 key attribute 设置唯一的值来标记以让 Vue 区分它们。
<style type="text/css">
.xxx-enter-active,
.xxx-leave-active {
transition: opacity .5s;
}
.xxx-enter,
.xxx-leave-to {
opacity: 0;
}
</style>
<body>
<div id="el">
<div><button @click="isEditing=!isEditing">改一下</button></div>
<!-- transition 多个元素要分别加上 key -->
<transition name="xxx">
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
</div>
</body>
<script type="text/javascript">
let vm = new Vue({
el: "#el",
data: {
isEditing: true
}
})
</script>
过渡模式
上面的例子中两个按钮都被重绘了,一个离开过渡的同时另一个开始进入过渡,这样的展示效果并不美观,对于这种情况可以设置过渡模式解决:
- in-out:新元素先进行过渡,完成之后当前元素过渡离开。
- out-in:当前元素先进行过渡,完成之后新元素过渡进入。
in-out效果:
<transition name="xxx" mode="in-out">...</transition>
out-in效果:
多个组件的过渡
多个组件的过渡不需要 key attribute,需要使用<component v-bind:is=“view”></component>动态组件。
<style type="text/css">
.xxx-enter-active, .xxx-leave-active {
transition: opacity .3s ease;
}
.xxx-enter, .xxx-leave-to{
opacity: 0;
}
</style>
<body>
<div id="el">
<div><button @click="changeView">改一下</button></div>
<transition name="xxx" mode="out-in">
<!--component + v-bind:is 作为动态组件 -->
<component v-bind:is="viewName"></component>
</transition>
</div>
</body>
<script type="text/javascript">
let vm = new Vue({
el: "#el",
data: {
viewName: 'v-a' //放置【当前组件】的变量
},
components: {
'v-a': {
template: '<div>Component A</div>'
},
'v-b': {
template: '<div>Component B</div>'
}
},
methods: {
//改变当前展示的组件
changeView() {
if (this.viewName === 'v-a') {
this.viewName = 'v-b'
} else {
this.viewName = 'v-a'
}
}
}
})
</script>
运行:
列表过渡
同时渲染整个列表(比如v-for),可以使用 <transition-group> 组件。特点如下:
- 不同于 <transition>,它会以一个真实元素呈现:默认为一个 <span>。也可以通过 tag attribute 自定义为其他元素。
- 过渡模式不可用,因为不再相互切换特有的元素。
- 内部元素 总是需要 提供唯一的 key 属性值。
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
进入/离开过渡
<style type="text/css">
.list-item {
display: inline-block;
margin-right: 10px;
}
.xxx-enter-active,
.xxx-leave-active {
transition: all 1s;
}
.xxx-enter,
.xxx-leave-to {
opacity: 0;
transform: translateY(30px);
}
</style>
<body>
<div id="el">
<button v-on:click="add">增加一个数</button>
<button v-on:click="remove">删除一个数</button>
<!-- tag设置外层真实标签为<p> -->
<transition-group name="xxx" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
</body>
<script type="text/javascript">
let vm = new Vue({
el: "#el",
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)
},
}
})
</script>
运行:
排序过渡
<transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move attribute,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name 属性来自定义前缀,也可以通过 move-class 属性手动设置。
<style type="text/css">
/* 使用v-move */
.xxx-move {
transition: transform 1s;
}
</style>
<body>
<div id="content">
<button @click="reverseArray">反序</button>
<div>
<transition-group name="xxx" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</transition-group>
</div>
</div>
</body>
<script>
var vm = new Vue({
el: '#content',
data: {
items: new Array()
},
methods: {
//反转数组
reverseArray() {
this.items.reverse();
}
},
//初始化数组
created() {
for (var i = 0; i < 10; i++) {
this.items.push(i);
}
}
});
</script>
运行: