Vue.js: 过渡与动画

最后更新:2026-08-26

过渡与动画让你的 Vue 应用更生动——元素进入/离开时平滑过渡,列表变化时优雅重排。Vue 内置了 <Transition><TransitionGroup> 组件,配合 CSS 类名或 JS 钩子实现各种动画效果。

掌握过渡动画是提升用户体验的关键——好的动画让应用感觉"流畅、响应式、专业"。

1. 你将学到


2. 一个"切换 Tab"硬切硬换的尴尬

(1) 痛点:组件突然消失/出现,UI 闪烁

Alice 的后台有个 Tab 切换器:

VUE
<!-- ❌ 翻车版:v-if 突然切换 -->
<template>
  <button @click="currentTab = 'home'">Home</button>
  <button @click="currentTab = 'profile'">Profile</button>
  
  <div v-if="currentTab === 'home'">Home Content</div>
  <div v-else-if="currentTab === 'profile'">Profile Content</div>
</template>

用户体验

(2) Vue Transition 解法:1 个标签,6 个 class

VUE
<!-- ✅ 正确版:平滑过渡 -->
<template>
  <button @click="currentTab = 'home'">Home</button>
  <button @click="currentTab = 'profile'">Profile</button>
  
  <Transition name="fade" mode="out-in">
    <div v-if="currentTab === 'home'" key="home">Home Content</div>
    <div v-else-if="currentTab === 'profile'" key="profile">Profile Content</div>
  </Transition>
</template>

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

用户体验

(3) 收益

加上过渡后:


3. Transition 单元素

(1) 基本用法

VUE
<template>
  <button @click="show = !show">Toggle</button>
  
  <Transition name="fade">
    <p v-if="show">Hello Vue</p>
  </Transition>
</template>

<style>
/* 6 个 CSS class,Vue 自动添加/移除 */
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s ease;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}
</style>

(2) 6 种 CSS class 详解

Class 触发时机 用途
v-enter-from 元素插入前 起始状态
v-enter-active 元素插入整个过程 过渡过程
v-enter-to 元素插入后 结束状态
v-leave-from 元素离开前 起始状态
v-leave-active 元素离开整个过程 过渡过程
v-leave-to 元素离开后 结束状态

(3) 命名规则

VUE
<!-- v-bind:name="fade" → 6 个 class 是 .fade-xxx -->
<Transition name="fade">...</Transition>

<!-- 不写 name → 默认 v-xxx(6 个 class 是 .v-xxx)-->
<Transition>...</Transition>

<!-- 动态 name -->
<Transition :name="transitionName">...</Transition>

(4) 3 种模式

VUE
<!-- 1. 默认模式:进入和离开同时进行(重叠) -->
<Transition name="fade">
  <div v-if="show">Content</div>
</Transition>

<!-- 2. out-in:先离开后进入(推荐,避免重叠) -->
<Transition name="fade" mode="out-in">
  <div v-if="show">Content</div>
</Transition>

<!-- 3. in-out:先进入后离开(少见) -->
<Transition name="fade" mode="in-out">
  <div v-if="show">Content</div>
</Transition>

4. 5 种 CSS 动画

(1) 淡入淡出(最常用)

CSS
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}

(2) 滑动

CSS
.slide-enter-active, .slide-leave-active {
  transition: transform 0.3s;
}
.slide-enter-from {
  transform: translateX(-100%);
}
.slide-leave-to {
  transform: translateX(100%);
}

(3) 缩放

CSS
.scale-enter-active, .scale-leave-active {
  transition: transform 0.3s;
}
.scale-enter-from, .scale-leave-to {
  transform: scale(0);
}

(4) 旋转

CSS
.rotate-enter-active {
  transition: transform 1s;
}
.rotate-enter-from {
  transform: rotate(0deg);
}
.rotate-leave-active {
  animation: bounce 0.5s;
}
@keyframes bounce {
  0% { transform: scale(0); }
  50% { transform: scale(1.2); }
  100% { transform: scale(1); }
}

(5) 组合动画

CSS
.combo-enter-active, .combo-leave-active {
  transition: all 0.3s;
}
.combo-enter-from {
  opacity: 0;
  transform: translateX(-50px) scale(0.8);
}
.combo-leave-to {
  opacity: 0;
  transform: translateX(50px) scale(0.8);
}

5. TransitionGroup 列表动画

(1) 基本用法

VUE
<template>
  <button @click="add">Add</button>
  
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
      <button @click="remove(item.id)">×</button>
    </li>
  </TransitionGroup>
</template>

<script setup>
const { ref } = Vue
const items = ref([
  { id: 1, name: 'Apple' },
  { id: 2, name: 'Banana' }
])

function add() {
  const id = Date.now()
  items.value.push({ id, name: `Item ${id}` })
}

function remove(id) {
  items.value = items.value.filter(i => i.id !== id)
}
</script>

<style>
/* 6 个 class,但用于列表项的 enter/leave/move */
.list-enter-active, .list-leave-active {
  transition: all 0.5s;
}
.list-enter-from, .list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
.list-leave-active {
  position: absolute;  /* 离开时脱离文档流 */
}
/* 列表项移动时的动画 */
.list-move {
  transition: transform 0.5s;
}
</style>

(2) 关键点:.list-move

CSS
/* 列表项位置变化时(如排序),自动应用 .list-move */
.list-move {
  transition: transform 0.5s;
}

(3) 5 大列表动画场景

场景 动画
添加项 淡入 + 下滑
删除项 淡出 + 左滑
排序 平滑移动到新位置
筛选 离开的淡出,保留的平滑过渡
拖拽 实时跟随鼠标

6. JavaScript 钩子

(1) 8 个 JS 钩子

VUE
<template>
  <Transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
    :css="false"  <!-- 禁用 CSS class -->
  >
    <p v-if="show">Hello</p>
  </Transition>
</template>

<script setup>
import gsap from 'gsap'

function beforeEnter(el) {
  console.log('Before enter:', el)
}

function enter(el, done) {
  gsap.to(el, {
    opacity: 1,
    y: 0,
    duration: 0.5,
    onComplete: done  // 通知完成
  })
}

function afterEnter(el) {
  console.log('After enter:', el)
}

// leave / beforeLeave / afterLeave 类似
</script>

(2) GSAP 集成(推荐)

BASH
npm install gsap
VUE
<template>
  <Transition :css="false" @enter="onEnter" @leave="onLeave">
    <p v-if="show">Hello GSAP</p>
  </Transition>
</template>

<script setup>
import gsap from 'gsap'

function onEnter(el, done) {
  gsap.fromTo(el, 
    { opacity: 0, y: 50 },
    { opacity: 1, y: 0, duration: 0.5, onComplete: done }
  )
}

function onLeave(el, done) {
  gsap.to(el, { opacity: 0, y: -50, duration: 0.3, onComplete: done })
}
</script>

7. 第三方动画库

(1) Animate.css 集成

BASH
npm install animate.css
JS
// main.js
import 'animate.css'
VUE
<template>
  <Transition
    enter-active-class="animate__animated animate__fadeIn"
    leave-active-class="animate__animated animate__fadeOut"
  >
    <p v-if="show">Hello</p>
  </Transition>
</template>

(2) @vueuse/motion

BASH
npm install @vueuse/motion
JS
// main.js
import { MotionPlugin } from '@vueuse/motion'
app.use(MotionPlugin)
VUE
<template>
  <div v-motion :initial="{ opacity: 0, y: 50 }" :enter="{ opacity: 1, y: 0 }">
    Fade in from below
  </div>
</template>

(3) GSAP 推荐场景

推荐度 适用
CSS 动画 ⭐⭐⭐⭐⭐ 简单过渡、淡入淡出
Animate.css ⭐⭐⭐⭐ 大量预设动画
@vueuse/motion ⭐⭐⭐⭐ 声明式、Vue 集成好
GSAP ⭐⭐⭐⭐⭐ 复杂时间线、物理动画

8. 完整示例:5 大过渡场景

▶ 示例:5 种 Vue Transition 动画(CDN 全支持)

HTML 📖 仅展示
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

<style>
button { padding: 6px 12px; margin: 4px; background: #42b883; color: white; border: none; border-radius: 4px; cursor: pointer; }
.modal-bg { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; }
.modal { background: white; padding: 2rem; border-radius: 8px; min-width: 300px; }

/* 5 种动画 class */
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }

.slide-enter-active, .slide-leave-active { transition: transform 0.3s; }
.slide-enter-from { transform: translateX(-100%); }
.slide-leave-to { transform: translateX(100%); }

.scale-enter-active, .scale-leave-active { transition: transform 0.3s; }
.scale-enter-from, .scale-leave-to { transform: scale(0); }

.combo-enter-active, .combo-leave-active { transition: all 0.3s; }
.combo-enter-from { opacity: 0; transform: translateY(-30px); }

.list-enter-active, .list-leave-active { transition: all 0.5s; }
.list-leave-to { opacity: 0; transform: translateX(-30px); }
.list-move { transition: transform 0.5s; }
</style>

<div id="app">
  <h4>1. Fade 淡入淡出</h4>
  <button @click="showFade = !showFade">{{ showFade ? '隐藏' : '显示' }}</button>
  <transition name="fade">
    <p v-if="showFade" style="padding: 0.5rem; background: #f0f9ff;">👋 Hello Fade</p>
  </transition>

  <h4>2. Slide 滑动</h4>
  <transition name="slide">
    <p v-if="showSlide" key="slide-text" style="padding: 0.5rem; background: #fef3c7;">👉 Slide 滑入</p>
  </transition>
  <button @click="showSlide = !showSlide">切换</button>

  <h4>3. Scale 缩放(Modal 弹窗)</h4>
  <button @click="showModal = true">打开 Modal</button>
  <transition name="scale">
    <div v-if="showModal" class="modal-bg" @click.self="showModal = false">
      <div class="modal">
        <h3>弹窗内容</h3>
        <button @click="showModal = false">关闭</button>
      </div>
    </div>
  </transition>

  <h4>4. TransitionGroup 列表动画</h4>
  <input v-model="newItem" placeholder="新项目">
  <button @click="addItem">添加</button>
  <transition-group name="list" tag="ul" style="list-style: none; padding: 0;">
    <li v-for="item in items" :key="item.id" style="padding: 4px; background: #ecfdf5; margin: 2px 0;">
      {{ item.name }} <button @click="removeItem(item.id)">×</button>
    </li>
  </transition-group>

  <h4>5. Combo 组合(Tab 切换)</h4>
  <button v-for="tab in tabs" :key="tab" @click="currentTab = tab">{{ tab }}</button>
  <transition name="combo" mode="out-in">
    <p :key="currentTab" style="padding: 0.5rem; background: #eff6ff;">
      当前 Tab: {{ currentTab }}
    </p>
  </transition>
</div>

<script>
const { createApp, ref } = Vue

const App = {
  setup() {
    const showFade = ref(true)
    const showSlide = ref(true)
    const showModal = ref(false)
    const currentTab = ref('Home')
    const tabs = ['Home', 'Profile', 'Settings']
    const newItem = ref('')
    const items = ref([
      { id: 1, name: '项目 1' },
      { id: 2, name: '项目 2' }
    ])
    let nextId = 3

    function addItem() {
      if (!newItem.value.trim()) return
      items.value.push({ id: nextId++, name: newItem.value })
      newItem.value = ''
    }
    function removeItem(id) {
      const idx = items.value.findIndex(i => i.id === id)
      if (idx >= 0) items.value.splice(idx, 1)
    }

    return {
      showFade, showSlide, showModal, currentTab, tabs,
      newItem, items, addItem, removeItem
    }
  }
}

createApp(App).mount('#app')
</script>
逻辑代码 87 行(超过 40 行限制,仅展示)

▶ 示例:6 种 CSS class 详解

Class 触发
.fade-enter-from 元素插入前
.fade-enter-active 元素插入过程
.fade-enter-to 元素插入后
.fade-leave-from 元素离开前
.fade-leave-active 元素离开过程
.fade-leave-to 元素离开后

▶ 示例:5 大第三方动画库(⚠️ 需 Vite)

命令 适用
CSS 无(内置) 简单
Animate.css npm i animate.css 预设动画
@vueuse/motion npm i @vueuse/motion 声明式
GSAP npm i gsap 复杂时间线
Motion One npm i motion 轻量 Web 动画

▶ 示例:5 个常见错误速查

错误 现象 解决
Transition 不生效 元素直接消失 <Transition> 包裹 + v-if
v-for 不动画 列表项没过渡 <TransitionGroup>
排序没动画 移动突兀 .list-move class
多个组件同时切换 重叠 key 区分 + mode="out-in"
CSS 动画太快/太慢 体验差 调整 transition duration 0.2-0.5s

▶ 示例:5 大实战场景

场景 动画
Tab 切换 fade + out-in
Modal 弹窗 scale + fade
列表添加/删除 list 动画
路由切换 fade + slide
通知提示 slide from top + auto-dismiss

❓ 常见问题

Q Transition 和 CSS @keyframes 怎么选?
A 简单过渡(opacity/transform)用 CSS transition。复杂动画(多步骤、物理)用 @keyframes 或 GSAP。Vue Transition 支持两种。
Q TransitionGroup 必须用 :key 吗?
A 是的。<TransitionGroup> 子元素必须用 :key 唯一标识,否则动画不触发。
Q 怎么实现"页面切换"动画?
A<router-view v-slot="{ Component }"> + <Transition> 包裹。每次路由变化,Component 会变化,触发 Transition。
Q mode="out-in" 有什么用?
A 让离开的组件先完成动画,再让进入的组件开始。避免两个组件同时显示(重叠)。
Q JS 钩子和 CSS class 能混用吗?
A 能。<Transition :css="false" @enter="onEnter"> 禁用 CSS class,只用 JS 钩子。或两者都启用(用 CSS class 做样式,JS 钩子做回调)。
Q 动画性能差怎么办?
A (1) 用 transform 和 opacity(GPU 加速);(2) 避免 layout 触发的属性(width/height/top);(3) 减少同时动画的元素数量。
Q Animate.css 怎么配合 Vue?
Aenter-active-class="animate__animated animate__fadeIn",把 Animate.css 的 class 名字映射到 Vue 的钩子。

📖 小节


📝 作业

  1. 基础题(难度⭐) 实现一个简单的淡入淡出 Modal:

    • 点击按钮打开 Modal
    • 淡入 0.3 秒
    • 点击关闭淡出
    • 背景遮罩同步淡入淡出
  2. 进阶题(难度⭐⭐) 实现一个 TransitionGroup 列表动画:

    • 5 个待办事项
    • 添加新项淡入(0.5 秒)
    • 删除项淡出(0.5 秒 + position absolute)
    • 排序时其他项平滑移动(.list-move)
  3. 挑战题(难度⭐⭐⭐) 实现完整的"页面切换动画"系统:

    1. 路由切换时 fade + slide 动画
    2. 列表添加/删除/排序的 3 种动画
    3. Modal 弹窗的 scale + fade 组合
    4. GSAP 集成的复杂时间线动画
    5. 5 个第三方动画库对比测试
    6. 性能优化(GPU 加速 + will-change)
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏