文章目录

Vue 动画:transition 组件与动画库

发布于 2026-04-04 15:03:53 · 浏览 19 次 · 评论 0 条

Vue 动画:transition 组件与动画库

Vue 提供了强大的内置过渡系统,通过 transition 组件,你可以轻松实现元素进入、离开时的动画效果。掌握这套机制后,还能无缝集成第三方动画库,实现更复杂的交互效果。


理解 transition 组件的核心机制

transition 组件的本质是一个容器,它会感知内部元素的挂载和卸载过程,并自动在恰当的时机为元素添加 CSS 类名。这些类名对应动画的不同阶段,你只需定义对应的 CSS 样式即可触发动画。

当元素进入 DOM 时,Vue 会依次添加 v-enter-fromv-enter-activev-enter-to 三个类名。当元素离开时,则会添加 v-leave-fromv-leave-activev-leave-to。其中 v-enter-activev-leave-active 贯穿整个动画过程,适合用来定义动画的持续时间、缓动函数等通用属性。


基础用法:纯 CSS 动画

首先来看最简单的情况:为单个元素的显示与隐藏添加淡入淡出效果。

<template>
  <button @click="show = !show">切换显示</button>
  <transition name="fade">
    <p v-if="show">这是一段需要动画效果的文本</p>
  </transition>
</template>

<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>

<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

解释:点击按钮切换 show 的值,p 元素会根据条件渲染或移除。transition 组件监听到这个变化,自动应用 CSS 类名。opacity 从 0 到 1(或反向)产生淡入淡出效果。

如果你需要在元素插入时应用不同的动画进入方式,比如从上方滑入,可以这样写:

.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.slide-fade-enter-from {
  transform: translateY(-20px);
  opacity: 0;
}

.slide-fade-leave-to {
  transform: translateY(20px);
  opacity: 0;
}

注意进入和离开动画可以使用不同的缓动函数。ease-out 适合进入动画,因为它让元素快速出现后缓慢稳定;cubic-bezier 提供了更细腻的物理质感。


JavaScript 钩子函数

有时候纯 CSS 无法满足复杂动画需求,比如需要根据动画进度做计算、与其他库协同,或者动画逻辑本身就很复杂。这时可以使用 Vue 提供的 JavaScript 钩子函数。

transition 组件暴露了八个钩子:@before-enter@enter@after-enter@enter-cancelled,以及对应的离开版本 @before-leave@leave@after-leave@leave-cancelled。在 enterleave 钩子中,你必须调用 done 回调函数来通知 Vue 动画已完成,否则 Vue 会认为动画仍在进行中。

<template>
  <transition
    @enter="onEnter"
    @leave="onLeave"
    :css="false"
  >
    <div v-if="show">动画元素</div>
  </transition>
</template>

<script setup>
import { ref } from 'vue'
const show = ref(true)

const onEnter = (el, done) => {
  // el 是被动画的元素
  // done 是必须调用的回调函数
  el.animate([
    { transform: 'scale(0)' },
    { transform: 'scale(1)' }
  ], {
    duration: 300,
    easing: 'ease-out'
  }).onfinish = done
}

const onLeave = (el, done) => {
  el.animate([
    { transform: 'scale(1)' },
    { transform: 'scale(0)' }
  ], {
    duration: 300,
    easing: 'ease-in'
  }).onfinish = done
}
</script>

:css="false" 是关键设置。它告诉 Vue 不要尝试操作 CSS 类名,完全由 JavaScript 控制动画。这样做能稍微提升性能,因为 Vue 不需要管理额外的 class。


列表动画:transition-group

当需要为列表中的每个元素添加动画时,应该使用 transition-group 组件。它与 transition 的核心区别在于:列表中的每个元素都是独立的动画单元,并且支持添加和移除元素时的重排动画。

<template>
  <button @click="addItem">添加项目</button>
  <button @click="removeItem">移除项目</button>

  <transition-group name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.content }}
    </li>
  </transition-group>
</template>

<script setup>
import { ref } from 'vue'

const items = ref([
  { id: 1, content: '项目一' },
  { id: 2, content: '项目二' },
  { id: 3, content: '项目三' }
])

let nextId = 4

const addItem = () => {
  items.value.push({ id: nextId++, content: `项目${nextId - 1}` })
}

const removeItem = () => {
  items.value.pop()
}
</script>

<style scoped>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

/* 重排时的过渡效果 */
.list-move {
  transition: transform 0.5s ease;
}
</style>
```

`list-move` 类是 `transition-group` 的特殊类名。当列表中的元素位置发生变化时,Vue 会自动为受影响的元素添加这个类,产生平滑的移动动画。这是实现列表拖拽排序动画效果的基础。

需要注意的是,`leave-active` 中的元素在离开动画期间仍会占用空间。如果希望元素离开后立即释放空间,可以给离开的元素添加 `position: absolute`:

```css
.list-leave-active {
  position: absolute;
}
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
```

但这会导致列表宽度不稳定,更稳妥的做法是配合 JavaScript 钩子,在动画结束后再移除元素。

---

## 集成 GSAP 动画库

GSAP(GreenSock Animation Platform)是业界最强大的 Web 动画库之一。它解决了不同浏览器间动画 API 不一致的问题,提供了时间线控制、滚动触发等高级功能。将 GSAP 与 Vue transition 组件结合,能大幅提升开发效率。

### 基础集成

在 `enter` 钩子中使用 GSAP:

```javascript
import gsap from 'gsap'

const onEnter = (el, done) => {
  gsap.fromTo(el, 
    { opacity: 0, y: 50 },
    { 
      opacity: 1, 
      y: 0, 
      duration: 0.5, 
      ease: 'power2.out',
      onComplete: done
    }
  )
}

const onLeave = (el, done) => {
  gsap.to(el, { 
    opacity: 0, 
    y: -50, 
    duration: 0.3, 
    ease: 'power2.in',
    onComplete: done
  })
}
```

### 时间线控制

GSAP 的时间线(Timeline)功能允许你编排多个动画的先后顺序,这在复杂场景中非常有用:

```javascript
const onEnter = (el, done) => {
  const tl = gsap.timeline({ onComplete: done })
  
  tl.fromTo(el,
    { opacity: 0, scale: 0.8 },
    { opacity: 1, scale: 1, duration: 0.3, ease: 'back.out(1.7)' }
  )
  .fromTo(el.querySelector('.inner'),
    { opacity: 0, y: 20 },
    { opacity: 1, y: 0, duration: 0.2 }
  )
}
```

`back.out(1.7)` 中的 `1.7` 是回弹幅度。值越大,元素到达终点后会 overshoot(超出)后再回弹,产生轻微的弹性效果。

### 列表动画实战

使用 GSAP 实现更精致的列表动画:

```javascript
const onEnter = (el, done) => {
  gsap.fromTo(el,
    { opacity: 0, x: -50 },
    {
      opacity: 1,
      x: 0,
      duration: 0.4,
      ease: 'power3.out',
      onComplete: done,
      delay: el.dataset.index * 0.1  // 依次延迟进入
    }
  )
}

const onLeave = (el, done) => {
  gsap.to(el, {
    opacity: 0,
    x: 50,
    duration: 0.3,
    ease: 'power2.in',
    onComplete: done
  })
}
```

`delay: el.dataset.index * 0.1` 让列表元素依次延迟进入,形成瀑布流效果。注意在模板中为每个元素绑定 `data-index` 属性:

```html
<transition-group
  @enter="onEnter"
  @leave="onLeave"
  :css="false"
  tag="ul"
>
  <li 
    v-for="(item, index) in items" 
    :key="item.id"
    :data-index="index"
  >
    {{ item.content }}
  </li>
</transition-group>
```

---

## 集成 Anime.js

Anime.js 是另一个轻量且功能强大的动画库,以其简洁的 API 和出色的性能著称。

### 基础用法

```javascript
import anime from 'animejs/lib/anime.es.js'

const onEnter = (el, done) => {
  anime({
    targets: el,
    opacity: [0, 1],
    translateY: [30, 0],
    easing: 'easeOutExpo',
    duration: 600,
    complete: done
  })
}

const onLeave = (el, done) => {
  anime({
    targets: el,
    opacity: [1, 0],
    translateY: [0, -30],
    easing: 'easeInExpo',
    duration: 400,
    complete: done
  })
}
```

Anime.js 的数组语法(如 `translateY: [30, 0]`)表示从 30 过渡到 0,这在定义起始状态时非常方便。

### 关键帧动画

Anime.js 支持关键帧,让你能精确控制动画的每个阶段:

```javascript
const onEnter = (el, done) => {
  anime({
    targets: el,
    keyframes: [
      { opacity: 0, scale: 0.5, duration: 0 },
      { opacity: 1, scale: 1.2, duration: 200, easing: 'easeOutQuad' },
      { scale: 1, duration: 150, easing: 'easeOutBounce' }
    ],
    complete: done
  })
}
```

这个动画包含三个阶段:初始状态、快速放大并淡入、回弹到正常大小。关键帧让复杂动画的实现变得直观可控。

---

## 性能优化与最佳实践

### 使用 will-change 提示浏览器

对于复杂动画,提前告知浏览器哪些属性即将变化,能让浏览器进行优化准备:

```css
.animated-element {
  will-change: transform, opacity;
}
```

但要注意:`will-change` 会占用额外内存,不要过度使用或在动画结束后忘记移除。

### 优先使用 transform 和 opacity

浏览器的合成线程能高效处理 `transform` 和 `opacity` 的变化,不会触发重排(reflow)或重绘(repaint)。尽可能让动画只涉及这两个属性,其他属性的动画性能开销会大得多。

### 避免在动画过程中读取布局信息

在 JavaScript 动画中,如果触发布局信息读取(如 `offsetHeight`、`getBoundingClientRect`),会导致浏览器强制同步重排,严重影响性能。常见的解决方法是预先读取并缓存这些值:

```javascript
const onEnter = (el, done) => {
  const height = el.offsetHeight  // 只读取一次
  
  gsap.fromTo(el,
    { height: 0 },
    { height: height, duration: 0.3, onComplete: done }
  )
}
```

### 设置 :css="false"

当完全使用 JavaScript 控制动画时,务必设置 `:css="false"`。这能阻止 Vue 添加和移除 CSS 类,避免潜在的性能损耗和意外的样式冲突。

---

## 实战:表单验证反馈动画

结合 Vue 的响应式系统和动画能力,可以为表单验证创建直观的反馈效果:

```html
<template>
  <form @submit.prevent="submitForm">
    <div class="form-group">
      <input 
        v-model="email" 
        :class="{ error: emailError }"
        @blur="validateEmail"
        placeholder="输入邮箱"
      />
      <transition name="shake">
        <span v-if="emailError" class="error-msg">
          请输入有效的邮箱地址
        </span>
      </transition>
    </div>
    <button type="submit">提交</button>
  </form>
</template>

<script setup>
import { ref } from 'vue'
import gsap from 'gsap'

const email = ref('')
const emailError = ref(false)

const validateEmail = () => {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  emailError.value = !regex.test(email.value)
}

const submitForm = () => {
  validateEmail()
  if (!emailError.value) {
    // 提交逻辑
    console.log('表单提交成功')
  }
}
</script>

<style scoped>
.shake-enter-active {
  animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
}

@keyframes shake {
  10%, 90% { transform: translate3d(-1px, 0, 0); }
  20%, 80% { transform: translate3d(2px, 0, 0); }
  30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
  40%, 60% { transform: translate3d(4px, 0, 0); }
}

.error {
  border-color: #ff4d4f;
}

.error-msg {
  color: #ff4d4f;
  font-size: 12px;
}
</style>

当用户输入无效邮箱并离开输入框时,错误信息会以抖动动画的形式出现,有效吸引用户注意力。


选择合适的工具

纯 CSS 动画适合简单的状态切换,如淡入淡出、滑动等,开发成本低、性能好。JavaScript 钩子配合 GSAP 适合需要精细控制的复杂动画,尤其当动画具有时序依赖或需要动态计算时。Anime.js 则在轻量级场景和关键帧动画中表现优秀。

根据项目实际需求选择合适的方案,不必过度设计。多数情况下,CSS 动画配合 Vue 内置的 transition 组件已经足够应对日常开发需求。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文