Transition 组件是 Vue 提供的内置组件,为单个元素或组件提供动画过渡效果。所谓过渡效果,其实就是从一个状态变化到另一个状态的过程,比如:从隐藏到显示、从进入到离开等。
Transition 组件会在一个元素或组件进入和离开 DOM 时应用动画。
进入或离开可由以下的条件之一触发,这也是 Transition 组件进入或离开动画生效的条件:
-
由
v-if
所触发的切换 -
由
v-show
所触发的切换 -
由特殊元素
<component>
切换的动态组件 -
改变元素或组件的
key
属性
例如下面这个最基本的例子。这个例子实现了通过点击 Toggle 按钮,p 标签会在 0.5s 的时间里进行显示或隐藏的切换:
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
当一个 Transition 组件中的元素被插入或移除时,会发生下面这些事情:
-
Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是,则一些 CSS 过渡 class 会在适当的时机被添加和移除。
-
如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用。
-
如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。
Transition 组件支持使用 CSS 实现过渡效果,也支持使用 JavaScript 实现过渡效果,当然啦,Transition 是个灵活的组件,CSS 和 JavaScript 也可以结合使用,实现过渡效果。
总体来说,Transition 组件通过结合适当的 CSS 或 JavaScript 可以实现各种不同的进入或离开的过渡效果。
基于 CSS 的过渡效果
CSS 过渡 class
Transition 组件提供了 6 个 CSS class 应用于进入与离开过渡效果中。这 6 个 CSS class 分别代码了进入与离开过渡效果中 6 个不同的状态。
这 6 个 CSS class 分别为 v-enter-from
、v-enter-active
、v-enter-to
、v-leave-from
、v-leave-active
和 v-leave-to
。
v-enter-from
、v-enter-active
、v-enter-to
分别代表进入动画的起始状态、进入动画的生效状态和进入动画的结束状态。
v-leave-from
、v-leave-active
和 v-leave-to
分别代表离开动画的起始状态、离开动画的生效状态和离开动画的结束状态。
v-enter-from
会在元素插入之前添加,在元素插入完成后的下一帧移除。
v-enter-active
会应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。v-enter-active
可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
v-enter-to
代表进入动画的最终状态,在元素插入完成后的下一帧被添加(也就是 v-enter-from
被移除的同时),在过渡或动画完成之后移除。
v-leave-from
在离开过渡效果被触发时立即添加,在一帧后被移除。
v-leave-active
会应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。v-leave-active
可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
v-leave-to
代表离开动画的最终状态,在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from
被移除的同时),在过渡或动画完成之后移除。
v-enter-active
和 v-leave-active
给我们提供了为进入和离开动画指定不同速度曲线的能力。
为过渡效果命名
Transition 组件提供的 6 个 class 名都是 v
开头的,我们可以给 Transition 组件传一个 name
prop 声明一个过渡效果名。这样那 6 个 class 名字不会用 v
作前缀,而是以传入的 name
为开头。
比如,我们给 Transition 组件传入的 name
prop 为 fade:
<Transition name="fade">
...
</Transition>
对于一个有名字的过渡效果,对他起作用的过渡 class 不会以 v
作为前缀,而是以 fade
为前缀:
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
CSS 的 transition 与 CSS 的 animation
Transition 组件通常会搭配原生 CSS 过渡或原生 CSS 动画一起使用。
我们可以使用原生 transition
CSS 属性定义需要执行动画的属性、持续时间和速度曲线。
例如下面的例子,借助原生 transition
CSS 属性,在进入和离开动画中定义不同持续时间和速度曲线。
<Transition name="slide-fade">
<p v-if="show">hello</p>
</Transition>
/*
进入和离开动画可以使用不同
持续时间和速度曲线。
*/
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
原生 CSS 动画和 CSS transition 的应用方式基本上是相同的,只有一点不同,那就是 *-enter-from
不是在元素插入后立即移除,而是在一个 animationend
事件触发时被移除。
animationend
事件在 CSS 动画完成时触发。Element: animationend event
对于大多数的 CSS 动画,只需要简单地在 *-enter-active
和 *-leave-active
class 下声明他们,那 CSS 动画就会在过渡过程中生效。比如下面的例子:
<Transition name="bounce">
<p v-if="show" style="text-align: center;">
Hello here is some bouncy text!
</p>
</Transition>
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
自定义过渡 class
我们也可以向 Transition 组件传递以下 props 来指定自定义的过渡 class :
enter-from-class
enter-active-class
enter-to-class
leave-from-class
leave-active-class
leave-to-class
我们传入的这些 class 会覆盖相应阶段的默认 class 名,这在需要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用,比如 Animate.css :
<!-- 假设你已经在页面中引入了 Animate.css -->
<Transition
name="custom-classes"
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight"
>
<p v-if="show">hello</p>
</Transition>
同时使用 transition 和 animation
Transition 组件支持同时使用 transition 和 animation ,如果 transition 和 animation 同时使用的话,需要用户显示地传入 type
prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 animation
或 transition
。
如果传入的 type
是 animation
,则 Vue 内部会通过监听 animationend
得知过渡已结束。
如果传入的 type
是 transition
,则 Vue 内部会通过监听 transitionend
得知过渡已结束。
👆 图中代码源自 Vue.js 3.2.45 版本
type
prop 是传 animation
还是 transition
取决于你所应用的 CSS 规则。
如下面的例子,这个例子同时使用了 原生的 transition CSS 规则和 animation CSS 规则:
<script src="../../dist/vue.global.js"></script>
<style>
.box {
width: 200px;
height: 100px;
background-color: #42b983;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
/* Fade transition */
.fade-enter-active, .fade-leave-active {
/* 同时使用了 transition 和 animation */
transition: opacity 1s ease;
animation: slide-in 10s ease forwards; /* Animation */
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* Keyframes for animation */
@keyframes slide-in {
from {
transform: translateY(-50px);
}
to {
transform: translateY(0);
}
}
</style>
<div id="demo">
<button @click="toggle">Toggle</button>
<!-- type 为 animation -->
<transition name="fade" type="animation">
<div v-if="show" class="box">Hello, Vue!</div>
</transition>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const show = ref(false)
const toggle = () => {
show.value = !show.value
}
return {
show,
toggle
}
}
}).mount('#demo')
</script>
传入的 type
是 animation
,则过渡会在 10s 后结束。如果传入的 type
是 transition
,则过渡会在 1s 后结束。
如果仅仅使用 animation
或 transition
二者的其中之一,Vue 可以自动探测到正确的类型。具体原理是通过 window.getComputedStyle
方法获得 dom 上的样式规则,然后通过获得的样式规则比较 transition
和 animation
各自的持续时间得出。如果 transition
持续的时间长,则 type
是 transition
类型。如果 animation
的持续的时间长,则 type
是 animation
类型。
官方的 API 文档对 type
prop 的作用也总结得非常好:
深层级过渡与显式过渡时长
尽管 Transition 组件内部提供的过渡 class 仅能应用在 Transition 组件的直接子元素上,我们还是可以使用深层级的 CSS 选择器,在深层级的元素上触发过渡效果:
<Transition name="nested">
<div v-if="show" class="outer">
<div class="inner">
Hello
</div>
</div>
</Transition>
/* 应用于嵌套元素的规则 */
.nested-enter-active .inner,
.nested-leave-active .inner {
transition: all 0.3s ease-in-out;
}
.nested-enter-from .inner,
.nested-leave-to .inner {
transform: translateX(30px);
opacity: 0;
}
/* ... 省略了其他必要的 CSS */
我们还可以在深层元素上添加一个过渡延迟,从而创建一个带渐进延迟的动画序列:
/* 延迟嵌套元素的进入以获得交错效果 */
.nested-enter-active .inner {
transition-delay: 0.25s;
}
但是,这会带来一个小问题。默认情况下,Transition 组件会通过监听过渡根元素上的第一个 transitionend
或者 animationend
事件来尝试自动判断过渡何时结束。也就是说 Vue 是无法自动监测到嵌套的子元素的过渡是何时结束的。而在嵌套的过渡中,期望的行为应该是等待所有内部元素的过渡完成。
这种情况下,Transition 组件为我们提供了 duration
prop 来显示指定过渡的持续时间(以毫秒为单位)。我们可以计算出过渡的总持续时间,即延迟时间加外部和嵌套元素的过渡持续时间,传入 duration
prop 中。明确告知 Transition 组件总的过渡持续时间。
<Transition :duration="550">...</Transition>
完整的例子如下:
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<template>
<button @click="show = !show">Toggle</button>
<Transition :duration="550" name="nested">
<div v-if="show" class="outer">
<div class="inner">
Hello
</div>
</div>
</Transition>
</template>
<style>
.outer, .inner {
background: #eee;
padding: 30px;
min-height: 100px;
}
.inner {
background: #ccc;
}
/* 定义根元素进入动画和离开动画生效状态的过渡动效 */
.nested-enter-active, .nested-leave-active {
transition: all 0.3s ease-in-out;
}
/* 根元素离开动画延迟 0.25 秒生效 */
.nested-leave-active {
transition-delay: 0.25s;
}
/* 定义根元素进入动画起始状态和离开动画结束状态 */
.nested-enter-from,
.nested-leave-to {
transform: translateY(30px);
opacity: 0;
}
/* 定义嵌套元素进入动画生效状态和离开动画生效状态的过渡动效 */
.nested-enter-active .inner,
.nested-leave-active .inner {
transition: all 0.3s ease-in-out;
}
/* 嵌套元素进入动画延迟 0.25s 执行 */
.nested-enter-active .inner {
transition-delay: 0.25s;
}
/* 定义嵌套元素进入动画起始状态和离开动画结束状态 */
.nested-enter-from .inner,
.nested-leave-to .inner {
transform: translateX(30px);
/*
Hack around a Chrome 96 bug in handling nested opacity transitions.
This is not needed in other browsers or Chrome 99+ where the bug
has been fixed.
*/
opacity: 0.001;
}
</style>
如果有必要,Transition 组件的 duration
prop 还支持以对象的形式传入,分别指定进入和离开所需的时间:
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
性能考量
出于性能考量,我们要多使用 transform
和 opacity
之类的 CSS 属性来制作动画。因为使用这些属性制作动画非常高效:
-
他们在动画过程中不会影响到 DOM 结构,因此不会每一帧都触发昂贵的 CSS 布局重新计算。
-
大多数的现代浏览器都可以在执行
transform
动画时利用 GPU 进行硬件加速。
相比之下,像 height
或者 margin
这样的属性会触发 CSS 布局变动,因此执行它们的动画效果更昂贵,需谨慎使用。
JavaScript 钩子
Transition 组件除了提供 6 个 class 定义过渡动效过渡时不同的状态外,还提供了 11 个 JavaScript 钩子在不同的过渡时机中执行:
-
@before-enter
:在元素被插入到 DOM 之前被调用,用这个来设置元素的 "enter-from" 状态(进入动画的起始状态) -
@before-leave
:在 leave 钩子之前调用,即在离开过渡开始前调用,大多数时候,应该只会用到 leave 钩子。 -
@enter
:在元素被插入到 DOM 之后的下一帧被调用,用这个来开始进入动画。 -
@leave
:在离开过渡开始时调用,用这个来开始离开动画。 -
@appear
:如果传入了appear
为 true 的 prop ,则在过渡元素初次渲染时被调用。 注意需要过渡元素默认就在页面上才会调用此钩子。如果过渡元素一开始没有在页面上,经历了过渡才出现在页面上,则此钩子函数不会被调用。 -
@after-enter
:当进入过渡完成时调用。 -
@after-leave
:在离开过渡完成、且元素已从 DOM 中移除时调用。 -
@after-appear
:也是与appear
相关的钩子,当appear
prop 传入为 true ,且过渡元素默认在页面上,则此钩子在首次进入动画完成后调用。 -
@enter-cancelled
:当元素的进入过渡被中断或取消时调用。例如,在进入过渡过程中,元素被移除或条件变更导致过渡未完成。 -
@leave-cancelled
(v-show
only):当元素离开过渡被取消时会调用。注意有个前提,就是这个过渡是由v-show
触发的。 -
@appear-cancelled
:也是与appear
相关的钩子,当appear
prop 传入为 true ,且过渡元素默认在页面上,则此钩子在首次进入动画取消后调用。
@enter
和 @leave
钩子支持 done
回调函数。当传入 :css="false"
时,则完全由 JavaScript 全权控制过渡什么时候结束,这个时候 @enter
和 @leave
钩子的回调函数 done
就是必须的。 只有执行了 @enter
或 @leave
钩子的 done
回调函数,才算是真正完成了进入过渡动画或离开过渡动画。
@appear
和 @after-appear
钩子的简单示例
<script src="../../dist/vue.global.js"></script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
<div id="demo">
<button @click="toggle">Toggle</button>
<transition
name="fade"
appear
@appear="onAppear"
@after-appear="onAfterAppear"
>
<div v-if="show">Hello, Vue Transition!</div>
</transition>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
// 注意,show 初始值为 true ,
// @appear 和 @after-appear 钩子才会被调用
const show = ref(true)
const toggle = () => {
show.value = !show.value
}
const onAppear = () => {
console.log('onAppear == ')
}
const onAfterAppear = () => {
console.log('onAfterAppear == ')
}
return {
show,
toggle,
onAppear,
onAfterAppear
}
}
}).mount('#demo')
</script>
@enter-cancelled
钩子的简单示例
<script src="../../dist/vue.global.js"></script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
<div id="demo">
<button @click="toggle">Toggle</button>
<transition
name="fade"
@enter-cancelled="onEnterCancelled"
>
<div v-if="show">Hello, Vue Transition!</div>
</transition>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const show = ref(false)
const toggle = () => {
show.value = !show.value
if (show.value) {
setTimeout(() => {
show.value = false // 立即取消进入过渡
}, 100) // 100ms 后取消
}
}
const onEnterCancelled = (el) => {
// 处理进入过渡被取消的逻辑
console.log('Enter transition was cancelled:', el)
}
return {
show,
toggle,
onEnterCancelled,
}
}
}).mount('#demo')
</script>
@leave-cancelled
钩子的简单示例
<script src="../../dist/vue.global.js"></script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
<div id="demo">
<button @click="toggle">Toggle</button>
<transition
name="fade"
@leave-cancelled="onLeaveCancelled"
>
<div v-show="show">Hello, Vue Transition!</div>
</transition>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const show = ref(false)
const toggle = () => {
show.value = !show.value
if (show.value === false) {
setTimeout(() => {
show.value = true // 立即取消离开过渡
}, 800) // 800ms 后取消
}
}
const onLeaveCancelled = (el) => {
// 处理离开过渡被取消的逻辑
console.log('Leave transition was cancelled:', el)
}
return {
show,
toggle,
onLeaveCancelled,
}
}
}).mount('#demo')
</script>
@appear-cancelled
钩子的简单示例
<script src="../../dist/vue.global.js"></script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
<div id="demo">
<transition
name="fade"
appear
@appear-cancelled="onAppearCancelled"
>
<div v-if="show">Hello, Vue Transition!</div>
</transition>
</div>
<script>
const { createApp, ref, onMounted } = Vue
createApp({
setup() {
const show = ref(true)
const toggle = () => {
if (show.value) {
setTimeout(() => {
show.value = false // 立即取消 appear
}, 800) // 800ms 后取消
}
}
const onAppearCancelled = (el) => {
// appear 被取消的逻辑
console.log('appear transition was cancelled:', el)
}
onMounted(() => {
toggle()
})
return {
show,
toggle,
onAppearCancelled,
}
}
}).mount('#demo')
</script>
上面提到的 JavaScript 钩子可以和 CSS 过渡或动画结合使用,也可以单独使用。
在使用仅由 JavaScript 执行的动画时,建议添加一个 :css="false"
prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果:
<Transition
...
:css="false"
>
...
</Transition>
纯粹使用 JavaScript 钩子实现过渡动画的例子
相对于使用 CSS 实现过渡动画,使用纯 JavaScript 实现过渡动画会麻烦一些。
例子1
<script src="../../dist/vue.global.js"></script>
<div id="demo">
<button @click="toggle">Toggle</button>
<transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@leave="onLeave"
:css="false"
>
<div v-if="show">Hello, Vue Transition!</div>
</transition>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const show = ref(false)
const toggle = () => {
show.value = !show.value
}
const onBeforeEnter = (el) => {
// 在进入之前设置初始样式
el.style.opacity = 0
}
const onEnter = (el, done) => {
// 进入时的过渡效果
el.offsetHeight; // 强制重排,才能使过渡生效
el.style.transition = `opacity 3s`
el.style.opacity = 1;
done() // 过渡完成后调用 done
}
const onLeave = (el, done) => {
// 离开时的过渡效果
el.style.transition = `opacity 3s`
el.style.opacity = 0;
// 过渡完成后调用 done
setTimeout(() => {
done()
}, 3000) // 与过渡时间一致
}
return {
show,
toggle,
onBeforeEnter,
onEnter,
onLeave,
}
}
}).mount('#demo')
</script>
没有调用 @enter
和 @leave
钩子的 done
回调函数,在完成离开动画后,过渡元素未真正移除,这是不符合期望的:
如果调用了 @enter
和 @leave
钩子的 done
回调函数,在完成离开动画后,过渡元素会被移除,这才是符合期望的:
例子2
<script src="../../dist/vue.global.js"></script>
<div id="demo">
<button @click="toggle">Toggle</button>
<transition
@enter="onEnter"
@leave="onLeave"
:css="false"
>
<div v-if="show">Hello, Vue Transition!</div>
</transition>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const show = ref(false)
const toggle = () => {
show.value = !show.value
}
const onEnter = (el, done) => {
// 进入过渡开始时的操作
el.style.opacity = 0
// 使用 requestAnimationFrame 确保样式应用后再进行过渡
requestAnimationFrame(() => {
el.style.transition = 'opacity 3s';
el.style.opacity = 1;
done(); // 过渡完成
});
}
const onLeave = (el, done) => {
// 离开过渡开始时的操作
el.style.transition = 'opacity 3s'
el.style.opacity = 0;
// 过渡完成后调用 done
setTimeout(() => {
done();
}, 3000); // 与过渡时间一致
}
return {
show,
toggle,
onEnter,
onLeave,
}
}
}).mount('#demo')
</script>
可复用过渡效果
得益于 Vue 的组件系统,过渡效果可以被封装复用。要创建一个可被复用的过渡,只需要为 Transition 组件创建一个包装组件,并向内传入插槽内容:
<!-- MyTransition.vue -->
<script>
// JavaScript 钩子逻辑...
</script>
<template>
<!-- 包装内置的 Transition 组件 -->
<Transition
name="my-transition"
@enter="onEnter"
@leave="onLeave">
<slot></slot> <!-- 向内传递插槽内容 -->
</Transition>
</template>
<style>
/*
必要的 CSS...
注意:避免在这里使用 <style scoped>
因为那不会应用到插槽内容上
*/
</style>
现在 MyTransition
可以在导入后像内置组件那样使用了:
<MyTransition>
<div v-if="show">Hello</div>
</MyTransition>
出现时过渡
如果想在某个节点初次渲染时应用一个过渡效果,可以添加 appear
prop:
<Transition appear>
...
</Transition>
元素间过渡
除了通过 v-if
/ v-show
切换一个元素,我们也可以通过 v-if
/ v-else
/ v-else-if
在几个元素间进行切换,只要确保任一时刻只会有一个元素被渲染即可:
<Transition>
<button v-if="docState === 'saved'">Edit</button>
<button v-else-if="docState === 'edited'">Save</button>
<button v-else-if="docState === 'editing'">Cancel</button>
</Transition>
完整的例子如下:
<script src="../../../dist/vue.global.js"></script>
<style>
.btn-container {
display: inline-block;
position: relative;
height: 1em;
}
button {
position: absolute;
}
/* 定义进入动画生效状态和离开动画生效状态过渡动效 */
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.25s ease-out;
}
/* 定义进入动画起始状态 */
.slide-up-enter-from {
opacity: 0;
transform: translateY(30px);
}
/* 定义离开动画结束状态 */
.slide-up-leave-to {
opacity: 0;
transform: translateY(-30px);
}
</style>
<div id="demo">
<span style="margin-right: 20px">
Click to cycle through states:
</span>
<div class="btn-container">
<transition
name="slide-up"
>
<button
v-if="docState === 'saved'"
@click="docState = 'edited'"
>Edit</button>
<button
v-else-if="docState === 'edited'"
@click="docState = 'editing'"
>Save</button>
<button
v-else-if="docState === 'editing'"
@click="docState = 'saved'"
>Cancel</button>
</transition>
</div>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const docState = ref('saved')
return {
docState,
}
}
}).mount('#demo')
</script>
过渡模式
上面的例子,进入和离开的元素都是在同时开始动画的,因此我们不得不将它们设为 position: absolute
以避免二者同时存在时出现的布局问题。
然而,很多情况下这可能并不符合需求。我们可能想要先执行离开动画,然后在其完成之后再执行元素的进入动画。手动编排这样的动画是非常复杂的,好在我们可以通过向 Transition 组件传入一个 mode
prop 定义不同的过渡模式,来实现这个行为:
<Transition mode="out-in">
...
</Transition>
mode
,定义过渡模式,用于控制离开/进入过渡的时序。默认情况下是同时的。
mode?: 'in-out' | 'out-in' | 'default'
-
in-out
:先执行进入动画,后执行离开动画 -
out-in
:先执行离开动画,后执行进入动画 -
default
: 同时执行进入动画和离开动画
将之前的例子改为 mode="out-in"
,button 元素便不需要设置 absolute
定位了:
<script src="../../../dist/vue.global.js"></script>
<style>
.btn-container {
display: inline-block;
position: relative;
height: 1em;
}
button {
/*
使用 out-in 过渡模式后,
不需要将 button 设置为 absolute 定位
*/
/* position: absolute; */
}
/* 定义进入动画生效状态和离开动画生效状态过渡动效 */
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.25s ease-out;
}
/* 定义进入动画起始状态 */
.slide-up-enter-from {
opacity: 0;
transform: translateY(30px);
}
/* 定义离开动画结束状态 */
.slide-up-leave-to {
opacity: 0;
transform: translateY(-30px);
}
</style>
<div id="demo">
<span style="margin-right: 20px">
Click to cycle through states:
</span>
<div class="btn-container">
<transition
name="slide-up"
mode="out-in"
>
<button
v-if="docState === 'saved'"
@click="docState = 'edited'"
>Edit</button>
<button
v-else-if="docState === 'edited'"
@click="docState = 'editing'"
>Save</button>
<button
v-else-if="docState === 'editing'"
@click="docState = 'saved'"
>Cancel</button>
</transition>
</div>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const docState = ref('saved')
return {
docState,
}
}
}).mount('#demo')
</script>
组件间过渡
Transition 组件也可以作用于动态组件之间的切换:
<Transition name="fade" mode="out-in">
<component :is="activeComponent"></component>
</Transition>
完整的例子如下:
<script src="../../../dist/vue.global.js"></script>
<script type="text/x-template" id="comp-a">
<div>
Component A
</div>
</script>
<script>
// 定义 CompA 组件
const CompA = {
template: '#comp-a',
}
</script>
<script type="text/x-template" id="comp-b">
<div>
Component B
</div>
</script>
<script>
// 定义 CompB 组件
const CompB = {
template: '#comp-b',
}
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
<div id="demo">
<label>
<input type="radio" v-model="activeComponent" :value="CompA"> A
</label>
<label>
<input type="radio" v-model="activeComponent" :value="CompB"> B
</label>
<transition name="fade" mode="out-in">
<component :is="activeComponent"></component>
</transition>
</div>
<script>
const { createApp, shallowRef } = Vue
Vue.createApp({
setup() {
const activeComponent = shallowRef(CompA)
return {
activeComponent,
// 将 CompA、CompB 作为 setup 函数的返回值,
// CompA、CompB 才可以在模板中被访问
CompA,
CompB
}
}
}).mount('#demo')
</script>
动态过渡
Transition 组件的 props (比如 name
)也可以是动态的。这让我们可以根据状态变化动态地应用不同类型的过渡:
<Transition :name="transitionName">
<!-- ... -->
</Transition>
这个特性的用处是可以提前定义好多组 CSS 过渡或动画的 class,然后在它们之间动态切换。
你也可以根据你的组件的当前状态在 JavaScript 过渡钩子中应用不同的行为。最后,创建动态过渡的终极方式还是创建可复用的过渡组件,并让这些组件根据动态的 props 来改变过渡的效果。可以说,Transition 组件被设计的相当灵活。
使用 Key Attribute 过渡
有时为了触发过渡,你需要强制重新渲染 DOM 元素。
以计数器组件为例:
<script setup>
import { ref } from 'vue';
const count = ref(0);
setInterval(() => count.value++, 1000);
</script>
<template>
<Transition>
<span :key="count">{{ count }}</span>
</Transition>
</template>
如果不使用 key
attribute,则只有文本节点会被更新,因此不会发生过渡。但是,有了 key
属性,Vue 就知道在 count
改变时创建一个新的 span
元素,因此 Transition
组件有两个不同的元素在它们之间进行过渡。
完整的例子如下:
<script src="../../../dist/vue.global.js"></script>
<style>
span{
font-size: 4rem;
}
.wrapper{
position:relative;
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
position: absolute;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
<div id="demo">
<div class="wrapper">
<transition>
<span :key="count">{{ count }}</span>
</transition>
</div>
</div>
<script>
const { createApp, ref } = Vue
Vue.createApp({
setup() {
const count = ref(0)
setInterval(() => {
count.value++
}, 1000)
return {
count
}
}
}).mount('#demo')
</script>
总结
Transition 组件设计得非常灵活,不仅可以通过 css 实现过渡效果,也可通过 JavaScript 实现过渡效果,还支持出现时过渡、元素间过渡、组件间过渡以及设置过渡模式等。因此 Transition 组件非常值得深入掌握。他会成为你在今后开发过渡动画效果的利器。
Transition 组件支持的 Props:
interface TransitionProps {
/**
* 用于自动生成过渡 CSS class 名。
* 例如 `name: 'fade'` 将自动扩展为 `.fade-enter`、
* `.fade-enter-active` 等。
*/
name?: string
/**
* 是否应用 CSS 过渡 class。
* 默认:true
*/
css?: boolean
/**
* 指定要等待的过渡事件类型
* 来确定过渡结束的时间。
* 默认情况下会自动检测
* 持续时间较长的类型。
*/
type?: 'transition' | 'animation'
/**
* 显式指定过渡的持续时间。
* 默认情况下是等待过渡效果的根元素的第一个 `transitionend`
* 或`animationend`事件。
*/
duration?: number | { enter: number; leave: number }
/**
* 控制离开/进入过渡的时序。
* 默认情况下是同时的。
*/
mode?: 'in-out' | 'out-in' | 'default'
/**
* 是否对初始渲染使用过渡。
* 默认:false
*/
appear?: boolean
/**
* 用于自定义过渡 class 的 prop。
* 在模板中使用短横线命名,例如:enter-from-class="xxx"
*/
enterFromClass?: string
enterActiveClass?: string
enterToClass?: string
appearFromClass?: string
appearActiveClass?: string
appearToClass?: string
leaveFromClass?: string
leaveActiveClass?: string
leaveToClass?: string
}
Transition 组件支持的事件:
@before-enter
@before-leave
@enter
@leave
@appear
@after-enter
@after-leave
@after-appear
@enter-cancelled
@leave-cancelled
(v-show
only)@appear-cancelled