过渡
Vue 提供了两个内置组件,可以帮助我们处理因状态变化而产生的过渡和动画:
<Transition>用于在元素或组件进入和离开 DOM 时应用动画。本页将介绍它。<TransitionGroup>用于在v-for列表中对元素或组件的插入、移除或移动应用动画。这将在下一章中介绍。
除了这两个组件之外,我们也可以在 Vue 中使用其他技术来应用动画,例如切换 CSS 类,或者通过样式绑定实现由状态驱动的动画。这些额外技术将在动画技巧一章中介绍。
<Transition> 组件
<Transition> 是一个内置组件:这意味着它在任何组件的模板中都可直接使用,无需注册。它可用于对通过默认插槽传入的元素或组件应用进入和离开动画。进入或离开可以通过以下任一方式触发:
- 通过
v-if条件渲染 - 通过
v-show条件显示 - 通过
<component>特殊元素切换动态组件 - 更改特殊的
key属性
下面是最基础用法的示例:
template
<button @click="show = !show">切换</button>
<Transition>
<p v-if="show">你好</p>
</Transition>css
/* 接下来我们会解释这些类的作用! */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}你好
TIP
<Transition> 只支持单个元素或组件作为其插槽内容。如果内容是组件,那么该组件本身也必须只有一个根元素。
当 <Transition> 组件中的元素被插入或移除时,会发生以下过程:
Vue 会自动探测目标元素是否应用了 CSS 过渡或动画。如果有,就会在合适的时机添加/移除一些 CSS 过渡类。
如果存在 JavaScript 钩子监听器,那么这些钩子会在合适的时机被调用。
如果没有检测到 CSS 过渡/动画,并且也没有提供 JavaScript 钩子,那么插入和/或移除的 DOM 操作会在浏览器的下一帧动画中执行。
基于 CSS 的过渡
过渡类
进入/离开过渡会应用六个类。

v-enter-from:进入的起始状态。在元素插入之前添加,在元素插入后一帧移除。v-enter-active:进入的激活状态。在整个进入阶段都会应用。在元素插入之前添加,在过渡/动画结束时移除。这个类可用于定义进入过渡的持续时间、延迟和缓动曲线。v-enter-to:进入的结束状态。在元素插入后一帧添加(与移除v-enter-from同时),在过渡/动画结束时移除。v-leave-from:离开的起始状态。在离开过渡触发后立即添加,在一帧后移除。v-leave-active:离开的激活状态。在整个离开阶段都会应用。在离开过渡触发后立即添加,在过渡/动画结束时移除。这个类可用于定义离开过渡的持续时间、延迟和缓动曲线。v-leave-to:离开的结束状态。在离开过渡触发后一帧添加(与移除v-leave-from同时),在过渡/动画结束时移除。
v-enter-active 和 v-leave-active 让我们能够为进入/离开过渡指定不同的缓动曲线,后续章节会看到相关示例。
命名过渡
可以通过 name prop 为过渡命名:
template
<Transition name="fade">
...
</Transition>对于命名过渡,其过渡类名前缀会使用其名称而不是 v。例如,上面这个过渡应用的类将是 fade-enter-active 而不是 v-enter-active。fade 过渡对应的 CSS 应该如下所示:
css
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}CSS 过渡
<Transition> 最常与 原生 CSS 过渡配合使用,就像上面的基础示例那样。transition CSS 属性是一个简写属性,允许我们指定过渡的多个方面,包括应该被动画化的属性、过渡持续时间,以及 缓动曲线。
下面是一个更高级的示例,它对多个属性进行过渡,并为进入和离开设置不同的持续时间和缓动曲线:
template
<Transition name="slide-fade">
<p v-if="show">你好</p>
</Transition>css
/*
进入和离开动画可以使用不同的
持续时间和时间函数。
*/
.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 动画的应用方式与 CSS 过渡相同,区别在于 *-enter-from 不会在元素插入后立即移除,而是在 animationend 事件触发时移除。
对于大多数 CSS 动画,我们可以直接将其声明在 *-enter-active 和 *-leave-active 类下。下面是一个示例:
template
<Transition name="bounce">
<p v-if="show" style="text-align: center;">
这里有一些弹跳文本!
</p>
</Transition>css
.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);
}
}你好,这里有一段会弹跳的文字!
自定义过渡类
你也可以通过向 <Transition> 传入以下 props 来指定自定义过渡类:
enter-from-classenter-active-classenter-to-classleave-from-classleave-active-classleave-to-class
这些会覆盖常规的类名。当你想要将 Vue 的过渡系统与现有的 CSS 动画库结合使用时,这尤其有用,例如 Animate.css:
template
<!-- 假设页面中已引入 Animate.css -->
<Transition
name="custom-classes"
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight"
>
<p v-if="show">你好</p>
</Transition>同时使用过渡和动画
Vue 需要挂载事件监听器来判断过渡何时结束。根据应用的 CSS 规则类型,这个事件可以是 transitionend 或 animationend。如果你只使用其中一种,Vue 可以自动检测正确的类型。
不过,在某些情况下,你可能希望在同一个元素上同时使用两者,例如由 Vue 触发的 CSS 动画,以及 hover 时的 CSS 过渡效果。在这种情况下,你需要通过传入 type prop 显式声明你希望 Vue 关注的类型,其值可以是 animation 或 transition:
template
<Transition type="animation">...</Transition>嵌套过渡与显式过渡时长
虽然过渡类只会应用到 <Transition> 中的直接子元素,但我们仍然可以通过嵌套的 CSS 选择器来对嵌套元素应用过渡:
template
<Transition name="nested">
<div v-if="show" class="outer">
<div class="inner">
你好
</div>
</div>
</Transition>css
/* 作用于嵌套元素的规则 */
.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 已省略 */我们甚至可以在进入时给嵌套元素添加过渡延迟,从而创建交错的进入动画序列:
css
/* 延迟嵌套元素进入,以实现交错效果 */
.nested-enter-active .inner {
transition-delay: 0.25s;
}不过,这会带来一个小问题。默认情况下,<Transition> 组件会通过监听根过渡元素上的第一个 transitionend 或 animationend 事件,来自动判断过渡何时结束。对于嵌套过渡,期望的行为应该是等待所有内部元素的过渡都结束。
在这种情况下,你可以在 <Transition> 组件上使用 duration prop 显式指定过渡时长(以毫秒为单位)。总时长应当与内部元素的延迟加上过渡时长相匹配:
template
<Transition :duration="550">...</Transition>你好
如果需要,你也可以使用对象形式分别指定进入和离开的时长:
template
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>性能注意事项
你可能会注意到,上面展示的动画大多使用了 transform 和 opacity 之类的属性。这些属性之所以高效,是因为:
它们不会在动画过程中影响文档布局,因此不会在每一帧动画中触发昂贵的 CSS 布局计算。
大多数现代浏览器在对
transform进行动画时都可以利用 GPU 硬件加速。
相比之下,像 height 或 margin 这样的属性会触发 CSS 布局,因此动画成本要高得多,使用时应当谨慎。
JavaScript 钩子
你可以通过监听 <Transition> 组件上的事件,使用 JavaScript 介入过渡过程:
template
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>js
// 在元素插入 DOM 之前调用。
// 用于设置元素的 "enter-from" 状态
function onBeforeEnter(el) {}
// 在元素插入后的一帧调用。
// 用于开始进入动画。
function onEnter(el, done) {
// 调用 done 回调以表示过渡结束
// 与 CSS 结合使用时可选
done()
}
// 在进入过渡完成时调用。
function onAfterEnter(el) {}
// 在进入过渡于完成前被取消时调用。
function onEnterCancelled(el) {}
// 在离开钩子之前调用。
// 大多数情况下,你只需要使用 leave 钩子
function onBeforeLeave(el) {}
// 在离开过渡开始时调用。
// 用于开始离开动画。
function onLeave(el, done) {
// 调用 done 回调以表示过渡结束
// 与 CSS 结合使用时可选
done()
}
// 在离开过渡完成且
// 元素已从 DOM 中移除时调用。
function onAfterLeave(el) {}
// 仅在 v-show 过渡中可用
function onLeaveCancelled(el) {}这些钩子可以与 CSS 过渡 / 动画结合使用,也可以单独使用。
当使用仅 JavaScript 的过渡时,通常最好添加 :css="false" 属性。这会明确告诉 Vue 跳过自动 CSS 过渡检测。除了性能会稍微更好一些之外,这也可以防止 CSS 规则意外干扰过渡:
template
<Transition
...
:css="false"
>
...
</Transition>使用 :css="false" 时,我们还需要完全负责控制过渡何时结束。在这种情况下,@enter 和 @leave 钩子都需要 done 回调。否则,这些钩子会同步调用,过渡会立即完成。
这里有一个使用 GSAP 库 执行动画的演示。当然,你也可以使用任何你想要的其他动画库,例如 Anime.js 或 Motion One:
可复用过渡
过渡可以通过 Vue 的组件系统进行复用。要创建一个可复用过渡,我们可以创建一个组件来包裹 <Transition> 组件,并向下传递插槽内容:
vue
<script>
// JavaScript 钩子逻辑...
</script>
<template>
<!-- 包裹内置的 Transition 组件 -->
<Transition
name="my-transition"
@enter="onEnter"
@leave="onLeave">
<slot></slot> <!-- 向下传递插槽内容 -->
</Transition>
</template>
<style>
/*
必要的 CSS...
注意:这里避免使用 <style scoped>,因为它
不适用于插槽内容。
*/
</style>现在可以像使用内置版本一样导入并使用 MyTransition:
template
<MyTransition>
<div v-if="show">Hello</div>
</MyTransition>出现时过渡
如果你还想对节点的初始渲染应用过渡,可以添加 appear 属性:
template
<Transition appear>
...
</Transition>元素间过渡
除了使用 v-if / v-show 切换元素之外,我们还可以使用 v-if / v-else / v-else-if 在两个元素之间过渡,只要我们确保在任何时刻只显示一个元素即可:
template
<Transition>
<button v-if="docState === 'saved'">编辑</button>
<button v-else-if="docState === 'edited'">保存</button>
<button v-else-if="docState === 'editing'">取消</button>
</Transition>点击循环切换状态:
过渡模式
在前面的例子中,进入和离开的元素是同时播放动画的,因此我们不得不将它们设为 position: absolute,以避免当两个元素同时存在于 DOM 中时产生布局问题。
不过,在某些情况下这并不可行,或者说并不是我们想要的行为。我们可能希望先让离开的元素完成动画,再让进入的元素在离开动画完成之后才插入。如果手动协调这类动画会非常复杂——幸运的是,我们可以通过给 <Transition> 传入 mode 属性来启用这种行为:
template
<Transition mode="out-in">
...
</Transition>下面是使用 mode="out-in" 的前一个演示:
点击循环切换状态:
<Transition> 也支持 mode="in-out",尽管它的使用频率要低得多。
组件间过渡
<Transition> 也可以包裹 动态组件 使用:
template
<Transition name="fade" mode="out-in">
<component :is="activeComponent"></component>
</Transition>组件 A
动态过渡
像 name 这样的 <Transition> 属性也可以是动态的!它允许我们根据状态变化动态应用不同的过渡效果:
template
<Transition :name="transitionName">
<!-- ... -->
</Transition>当你使用 Vue 的过渡类约定定义了 CSS 过渡 / 动画,并且想在它们之间切换时,这会很有用。
你也可以根据组件的当前状态,在 JavaScript 过渡钩子中应用不同的行为。最后,创建动态过渡的终极方式是通过可复用过渡组件,它们接受 props 来改变所使用过渡的性质。听起来可能有点老套,但真正的限制其实只有你的想象力。
带有 Key 属性的过渡
有时你需要强制某个 DOM 元素重新渲染,才能触发过渡效果。
以这个计数器组件为例:
vue
<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 属性,只有文本节点会被更新,因此不会发生过渡。然而,有了 key 属性后,Vue 就知道每当 count 改变时都创建一个新的 span 元素,因此 Transition 组件就有了两个不同的元素之间可以进行过渡。
相关内容