Skip to content

Vue 中的过渡与动画

1. 动画实现的基础思路

在 Web 开发中,实现动画效果主要有两种技术途径:

  • CSS 过渡 (Transition) 与动画 (Animation)
    • 通过 transitionanimation 属性实现。
    • 性能较好,由浏览器直接处理。
  • JavaScript 动画
    • 通过 setTimeout / requestAnimationFrame 等方式,用 JS 动态改变元素样式(如位置、尺寸)。
    • 优点:控制更精细、灵活,可以实现暂停、倒放、处理复杂逻辑等。
    • 缺点:可能比 CSS 动画消耗更多性能。

2. 在 Vue 中应用原生动画方法

Vue 作为一个前端框架,本身不限制原生动画技术的使用。

2.1 CSS 方式:动态添加/移除类

最直接的方式是在合适的时机,通过 Vue 的数据绑定动态地为一个元素添加或移除一个带有 transitionanimation 属性的 CSS 类。

html
<div :class="{ 'animate-class': shouldAnimate }"></div>
css
/* CSS */
.animate-class {
  transition: all 0.5s ease;
  transform: translateX(100px);
}

2.2 JS 方式:数据驱动与第三方库

通过 JS 控制一个数据,并将这个数据绑定到元素的 style 上。通过改变这个数据,驱动视图更新,从而形成动画。

  • 核心思路:

    1. <template> 中,将元素的位置、尺寸等样式信息绑定到 data 中的数据。
    2. <script> 中,使用 setTimeoutsetIntervalrequestAnimationFrame 逐步改变这些数据。
    3. 数据一变,视图自动更新,形成动画。
  • 可借助的第三方库:

    • Velocity.js
    • anime.js (非常流行)

这些库能帮助我们更好地控制数据在指定时间内的状态变化。我们只需要在库的回调中更新 Vue 的 data 即可。

3. 内置组件 <transition>

Vue 考虑到元素的出现和消失是最常见的动画场景,因此提供了一个内置组件 <transition> 来简化这类动画的开发。

3.1 作用与适用场景

  • 作用: 自动监听其插槽内唯一根元素的出现和消失,并在合适的时机应用 CSS 过渡或动画效果。
  • 适用场景: 主要用于单个元素的进入 (Enter)离开 (Leave) 动画。例如由 v-ifv-show 控制的元素。
  • 注意: <transition> 组件本身不渲染任何 DOM 元素,它只是一个包装器。

3.2 核心要点

  1. 唯一根元素: 插槽内必须有且仅有一个根元素。可以使用 v-if/v-else 来切换两个元素,因为在同一时刻只有一个元素被渲染。
  2. 监控对象:
    • v-if 条件渲染导致的 DOM 元素新增或移除
    • v-show 条件渲染导致的元素 display 样式的显示或隐藏

3.3 过渡流程与 CSS 类名

<transition> 组件在元素进入和离开的不同阶段,会自动添加和移除特定的 CSS 类名。我们可以通过定义这些类的样式来实现动画。

类名的默认前缀是 v-,如果给 <transition> 组件设置了 name 属性,例如 name="fade",则前缀会变为 fade-

进入过渡 (Enter)

当一个元素被插入时,会依次发生以下事件:

  1. v-enter: 进入过渡的起始状态。元素被插入后立即添加,在下一帧被移除。用于定义动画开始前的样式(如 opacity: 0)。
  2. v-enter-active: 进入过渡的激活状态。在整个进入过程中生效,定义过渡或动画的持续时间、延迟和曲线(如 transition: opacity 0.5s ease;)。
  3. v-enter-to: 进入过渡的结束状态。在 v-enter 被移除后(即下一帧)立即添加,在过渡/动画完成后移除。定义动画结束后的样式(如 opacity: 1)。

流程图解:

元素插入 -> 添加 v-enter, v-enter-active -> [下一帧] -> 移除 v-enter, 添加 v-enter-to -> [过渡/动画结束] -> 移除 v-enter-active, v-enter-to

离开过渡 (Leave)

当一个元素被移除时,会依次发生以下事件:

  1. v-leave: 离开过渡的起始状态。元素被删除时立即添加,在下一帧被移除。
  2. v-leave-active: 离开过渡的激活状态。在整个离开过程中生效,定义过渡或动画。
  3. v-leave-to: 离开过渡的结束状态。在 v-leave 被移除后(即下一帧)立即添加,在过渡/动画完成后移除。用于定义元素离开后的最终状态(如 opacity: 0)。

流程图解:

触发删除 -> 添加 v-leave, v-leave-active -> [下一帧] -> 移除 v-leave, 添加 v-leave-to -> [过渡/动画结束] -> 移除 v-leave-active, v-leave-to -> 元素从 DOM 中删除

代码示例:淡入淡出

html
<button @click="show = !show">Toggle</button>
<transition name="fade">
  <p v-if="show">hello</p>
</transition>
css
/* 进入的起始状态 和 离开的结束状态 */
.fade-enter, .fade-leave-to {
  opacity: 0;
}

/* 进入和离开的整个过程 */
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}

3.4 自定义过渡类名

除了使用 name 属性外,还可以通过 props 直接指定类名,这在需要结合第三方 CSS 动画库(如 Animate.css)时非常有用。

html
<transition
  enter-active-class="animated bounceInLeft"
  leave-active-class="animated bounceOutRight">
  <p v-if="show">hello</p>
</transition>

3.5 初始渲染过渡 (appear)

默认情况下,<transition> 不会在初始渲染时播放动画。可以通过添加 appear 属性来启用。

html
<transition name="fade" appear>
  <p>Hello</p>
</transition>

4. 多元素及动态组件的过渡

4.1 使用 v-if / v-else

<transition> 可以包裹 v-if/v-else/v-else-if 结构,因为在任何时刻只有一个元素被渲染。

4.2 key 属性的重要性

当切换相同标签名的元素时(例如,从一个 <h1> 切换到另一个 <h1>),Vue 为了高效会就地复用这个元素,只更新其内容。这会导致过渡效果不触发。

为了让 Vue 将它们视为独立的元素并触发过渡,需要为它们添加唯一的 key 属性

html
<transition name="fade" mode="out-in">
  <h1 v-if="isTitle1" key="title1">Title 1</h1>
  <h1 v-else key="title2">Title 2</h1>
</transition>

4.3 过渡模式 (mode)

默认情况下,进入和离开的动画是同时发生的。mode 属性可以控制它们的执行顺序:

  • in-out: 新元素先过渡进入,完成后,旧元素再过渡离开。
  • out-in: 旧元素先过渡离开,完成后,新元素再过渡进入。(更常用)

4.4 动态组件 <component> 的过渡

<transition> 也可以包裹动态组件,当 :is 绑定的组件切换时,会触发过渡。

html
<transition name="fade" mode="out-in">
  <component :is="currentComponent"></component>
</transition>

5. 列表过渡 <transition-group>

对于 v-for 渲染的列表,需要使用 <transition-group> 组件。

5.1 与 <transition> 的区别

  1. 渲染元素: <transition-group> 默认会渲染一个 <span> 元素作为包裹容器。可以通过 tag 属性指定渲染成其他元素,如 tag="ul"
  2. 内部元素: 插槽内可以是多个元素(通常是 v-for 的结果)。
  3. key 是必须的: 列表中的每个元素都必须有唯一的 key 属性。

5.2 列表的进入与离开

列表项的进入和离开动画与 <transition> 的用法完全相同,使用相同的 CSS 过渡类。

5.3 列表的移动过渡 (v-move)

<transition-group> 最强大的功能是,当列表项的顺序发生改变时(如排序、删除某项),它会自动为正在移动的元素添加一个 v-move 类(如果设置了 name,则为 name-move)。

我们可以利用这个类来实现平滑的移动动画。

核心技巧: v-move 类通常只需要设置 transition 属性即可。Vue 会在背后通过 FLIP 技术自动处理元素的位置变换。

代码示例:平滑的列表增删和排序

html
<transition-group name="list" tag="ul">
  <li v-for="item in items" :key="item.id" class="list-item">
    {{ item.text }}
  </li>
</transition-group>
css
/* 进入和离开的动画 */
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}

/* 移动时的动画 */
.list-move {
  transition: transform 1s;
}

/* 解决移除元素时的塌陷问题 */
.list-leave-active {
  position: absolute;
}

特别注意: 当一个元素离开时,为了让其他元素能够平滑地移动到新位置,而不是瞬间“跳”过去,需要将离开的元素(即应用了 leave-active 类的元素)设置为 position: absolute,使其脱离文档流。

5.4 FLIP 动画思想简介

v-move 的平滑过渡效果背后是 FLIP (First, Last, Invert, Play) 技术的应用。

  1. First: 记录元素在变化的位置。
  2. Last: 让元素立即变化到最终位置,并记录这个位置。
  3. Invert: 计算出前后位置的差值,通过 transform 将元素瞬间移回(“反转”)到初始位置。
  4. Play: 移除 transform,并添加 transition 效果,让元素平滑地“播放”动画回到其最终位置。

由于第 2、3 步发生得极快,人眼观察不到,看到的只是第 4 步的平滑动画。Vue 帮我们处理了这一切。