这是我参与11月更文挑战的8天,活动详情查看:2021最后一次更文挑战
概述
在 Vue 中,我们可以通过多种方法实现过渡和动画效果,包括 class 和 style 以及 <transition> 标签实现单元素 / 组件及多元素 / 组件之间的过渡效果,通过 <transition-group> 标签实现列表过渡,通过 Vue 的响应性和组件系统实现状态过渡。
基于 class 的过渡和动画
/* 动画 */
@keyframes leftToRight {
0% {
transform: translateX(-100px);
}
50% {
transform: translateX(-50px);
}
0% {
transform: translateX(0px);
}
}
.animation {
animation: leftToRight 3s;
}
/* 过渡 */
.transition {
transition: 3s background-color ease;
}
.yellow {
background-color: yellow;
}
.skyblue {
background-color: skyblue;
}
const app = Vue.createApp({
data() {
return {
animation: {
animation: true
},
transition: {
transition: true,
skyblue: true,
yellow: false
}
}
},
methods: {
handleClickAnimation() {
this.animation.animation = !this.animation.animation
},
handleClickTransition() {
this.transition.skyblue = !this.transition.skyblue;
this.transition.yellow = !this.transition.yellow;
}
},
template:/*html*/ `
<div :class='animation'>Hello Animarion</div>
<button @click='handleClickAnimation'>change</button>
<div :class='transition'>Hello Transiton</div>
<button @click='handleClickTransition'>change</button>
`
})
const vm = app.mount("#root")
通过 style 控制
const app = Vue.createApp({
data() {
return {
styleObj: {
background: 'skyblue'
}
}
},
methods: {
handleClick() {
this.styleObj.background === 'skyblue' ?
this.styleObj.background = 'yellow' :
this.styleObj.background = 'skyblue'
}
},
template:/*html*/ `
<div class='transition' :style='styleObj'>Hello Transiton</div>
<button @click='handleClick'>change</button>
`
})
进入 / 离开过渡
当插入、更新和移除 DOM 时, Vue 提供了多种应用转换效果的方法。
单元素 / 组件的过渡
CSS 过渡
通过 <transition> 标签控制一个元素即单元素的过渡。元素从展示态变为隐藏态即出场,元素从展示态变为隐藏态即入场。下面的例子通过 v-if 来控制元素的渲染,除此之外还可以使用 v-show 控制元素的展示:
const app = Vue.createApp({
data() {
return {
show: false
}
},
methods: {
handleClick() {
this.show = !this.show
}
},
template:/*html*/`
<transition>
<div v-if='show'>Hello World</div>
</transition>
<button @click='handleClick'>change</button>
`
})
之后我们需要添加一些样式。
过渡 class
v-enter-from是指元素入场的开始状态,在元素被插入之后的下一帧移除;v-enter-to是指入场的结束状态,在过渡/动画完成之后移除;v-enter-active定义进入生效时的状态,在过渡/动画完成之后移除:
.v-enter-from {
opacity: 0;
}
.v-enter-active {
transition: opacity 3s ease-out;
}
.v-enter-to {
opacity: 1;
}
出场过程也有三个 class 切换,分别是v-leave-from、v-leave-active、v-leave-to。
上图解释了各个类的生命周期。
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些 class 名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter-from会替换为 my-transition-enter-from。
CSS 动画
CSS 动画的用法同 CSS 过渡,使用的也是 <transition> 标签。区别是在动画中 v-enter-from 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。
.v-enter-active {
animation: bounce-in 0.5s;
}
.v-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
/* ... */
}
自定义动画类名
通过以下属性可以自定义类名:enter-from-class、enter-active-class、enter-to-class、leave-from-class、leave-active-class、leave-to-class。
由此可以使用第三方动画库。
同时使用过渡和动画
在同时使用过渡和动画的情况下,<transition> 标签的 type attribute 可以设置以动画或过渡之一的时间为准,值可以是transition和animation。
.v-enter-active {
transition: opacity 3s ease-out;
animation: bounce-in 5s reverse;
}
<transition type='transiton'>...</transition>
上面的例子中,过渡和动画都在 3 秒后结束。
显性过渡持续时间
我们也可以通过duration设置一个显性的过渡持续时间,单位为毫秒,时间结束后过渡和动画效果都结束:
<transition :duration='1000'>...</transition>
还可以定制进入和移出的持续时间:
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
JavaScript 钩子
我们可以不使用 CSS 过渡和动画,转而使用 JS 的过渡和动画效果。
使用:css='false'跳过 CSS 检测,通过 JavaScript 钩子来定义动画效果。
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
:css="false"
>
<!-- ... -->
</transition>
methods: {
beforeEnter(el) {
el.style.color='red'
},
// 当与 CSS 结合使用时,回调函数 done 是可选的
enter(el, done) {
done() // 表示 enter 函数执行结束
},
// enter 执行结束之后才执行 afterEnter
afterEnter(el) {
// ...
},
enterCancelled(el) {
// ...
}
// ...
}
初始渲染的过渡
可以通过 appear attribute 设置节点在初始渲染的过渡:
<transition appear>
<!-- ... -->
</transition>
多个元素之间的过渡
我们可以通过v-if、v-else、v-else-if来实现元素之间的切换。默认情况下,<transition>元素的进入和离开是同时发生的,会导致同时出现两个元素。
同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式
in-out: 新元素先进行过渡,完成之后当前元素过渡离开。out-in: 当前元素先进行过渡,完成之后新元素过渡进入。
<transition mode='out-in'>
<div v-if='show'>Hello World</div>
<div v-else>Bye World</div>
</transition>
<button @click='handleClick'>change</button>
多个组件之间过渡
在组件中,我们除了使用v-if和v-show之外,还可以使用动态组件来控制组件的切换。
列表过渡
目前我们只说了单元素、多元素中某一个元素的动画效果。现在介绍多元素的动画。
列表的进入 / 离开过渡
我们可以通过<transition-group>来渲染整个列表。
const app = Vue.createApp({
data() {
return {
list: [1, 2, 3]
}
},
methods: {
handleClick() {
this.list.unshift(this.list.length + 1)
}
},
template:/*html*/ `
<transition-group>
<span class='list-item' v-for='item in list' :key='item'>{{item}}</span>
</transition-group>
<button @click='handleClick'>add</button>
`
})
.list-item {
display: inline-block;
margin-right: 10px;
}
.v-enter-from {
opacity: 0;
transform: translateY(30px);
}
.v-enter-active {
transition: all 2s ease;
}
这里的列表中的每一项需要提供一个唯一的key值,否则动画不生效。
此时有一个问题,列表的元素每增加一项,周围的元素会瞬间移动到他们所在的位置。
列表的移动过渡
为了解决刚刚提到的问题,我们可以使用v-move类来应用于元素改变的过程中。
.v-move {
transition: all .5s ease-in
}
状态过渡
当数据本身发生变化时,我们可以利用 Vue 的响应性和组件系统实现状态过渡的效果。
下面是一个例子:
const app = Vue.createApp({
data() {
return {
number: 1,
animateNumber: 1
}
},
methods: {
handleClick() {
this.number = 10;
if (this.animateNumber < this.number) {
const animation = setInterval(() => {
this.animateNumber += 1
if (this.animateNumber === 10) {
clearInterval(animation)
}
}, 100)
}
}
},
template:/*html*/ `
<div>{{animateNumber}}</div>
<button @click='handleClick'>add</button>
`
})
点击按钮时,数字会从 1 逐一增加到 10,它是以动画的形式展示出来的。