13-Vue3 动画与过渡
Vue3 动画与过渡
Vue3 的 Transition 和 TransitionGroup 组件让界面交互更加生动,从 CSS 类名变化到 JavaScript 钩子,全面掌握动画系统的每一个细节。
一、前言
动画不仅是视觉装饰,更是提升用户体验的重要手段。良好的过渡动画可以:
- 帮助用户理解界面状态的变化
- 缓解等待焦虑(加载动画)
- 引导用户注意力
- 增强界面的专业感和精致度
Vue3 继承了 Vue2 的过渡系统,并进行了优化和调整。本文将系统讲解 Vue3 中的动画与过渡,包括基础用法、JavaScript 钩子、列表动画以及与第三方库的集成。
二、Transition 组件
2.1 基本用法
<Transition> 是 Vue3 内置的过渡组件,包裹元素或组件后,在插入、更新或移除 DOM 时自动应用过渡类名。
<template>
<button @click="show = !show">切换显示</button>
<Transition>
<p v-if="show" class="greeting">你好,Vue3!</p>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<style scoped>
/* 进入动画 */
.v-enter-active {
transition: opacity 0.5s ease;
}
.v-enter-from {
opacity: 0;
}
.v-enter-to {
opacity: 1;
}
/* 离开动画 */
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-leave-from {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
</style>
2.2 Vue3 过渡类名变化
Vue3 对过渡类名进行了调整,这是与 Vue2 最大的区别之一:
| 状态 | Vue2 类名 | Vue3 类名 | 说明 |
|---|---|---|---|
| 进入开始 | v-enter |
v-enter-from |
元素插入前的初始状态 |
| 进入中 | v-enter-active |
v-enter-active |
整个进入过渡阶段 |
| 进入结束 | v-enter-to |
v-enter-to |
元素插入后的最终状态 |
| 离开开始 | v-leave |
v-leave-from |
元素移除前的初始状态 |
| 离开中 | v-leave-active |
v-leave-active |
整个离开过渡阶段 |
| 离开结束 | v-leave-to |
v-leave-to |
元素移除后的最终状态 |
关键变化:Vue3 将 v-enter 重命名为 v-enter-from,将 v-leave 重命名为 v-leave-from,使命名更加语义化。
2.3 自定义过渡类名
当需要集成第三方 CSS 动画库(如 Animate.css)时,可以使用自定义类名:
<template>
<Transition
enter-active-class="animate__animated animate__bounceIn"
leave-active-class="animate__animated animate__bounceOut"
>
<p v-if="show" class="animate__animated">Animate.css 动画</p>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<style>
/* 引入 Animate.css */
@import 'animate.css';
</style>
2.4 命名过渡
通过 name 属性可以自定义过渡类名的前缀:
<template>
<Transition name="fade">
<p v-if="show">命名过渡</p>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<style scoped>
/* 使用 fade- 前缀 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
2.5 CSS 过渡 vs CSS 动画
Vue 的过渡系统同时支持 CSS 过渡(transition)和 CSS 动画(animation):
<style scoped>
/* CSS 过渡方式 */
.slide-enter-active {
transition: transform 0.3s ease;
}
.slide-enter-from {
transform: translateX(-100%);
}
/* 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);
}
}
</style>
三、TransitionGroup 组件
3.1 列表过渡基础
<TransitionGroup> 用于对 v-for 列表进行过渡动画:
<template>
<div>
<button @click="addItem">添加项目</button>
<button @click="shuffle">打乱顺序</button>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item.id">
{{ item.text }}
<button @click="removeItem(item.id)">删除</button>
</li>
</TransitionGroup>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, text: '项目 1' },
{ id: 2, text: '项目 2' },
{ id: 3, text: '项目 3' }
])
let nextId = 4
const addItem = () => {
items.value.push({ id: nextId++, text: `项目 ${nextId - 1}` })
}
const removeItem = (id) => {
const index = items.value.findIndex(item => item.id === id)
if (index > -1) {
items.value.splice(index, 1)
}
}
const shuffle = () => {
items.value = items.value.sort(() => Math.random() - 0.5)
}
</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-leave-active {
position: absolute;
}
/* 列表项移动时的过渡 */
.list-move {
transition: transform 0.5s ease;
}
</style>
3.2 FLIP 动画原理
TransitionGroup 内部使用了 FLIP 动画技术:
- First:记录元素的初始位置
- Last:记录元素的最终位置
- Invert:计算并应用反向偏移
- Play:播放过渡动画
TransitionGroup 会自动处理这个过程,开发者只需定义 .v-move 类即可。
3.3 列表排序动画
<template>
<div class="todo-app">
<div class="filters">
<button @click="sortBy = 'default'">默认</button>
<button @click="sortBy = 'priority'">按优先级</button>
<button @click="sortBy = 'date'">按日期</button>
</div>
<TransitionGroup name="todo" tag="div" class="todo-list">
<div
v-for="todo in sortedTodos"
:key="todo.id"
class="todo-item"
:class="`priority-${todo.priority}`"
>
<span>{{ todo.title }}</span>
<span>{{ todo.date }}</span>
</div>
</TransitionGroup>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const sortBy = ref('default')
const todos = ref([
{ id: 1, title: '完成项目文档', priority: 'high', date: '2024-01-15' },
{ id: 2, title: '修复 Bug', priority: 'medium', date: '2024-01-10' },
{ id: 3, title: '代码审查', priority: 'low', date: '2024-01-20' },
{ id: 4, title: '部署上线', priority: 'high', date: '2024-01-12' }
])
const sortedTodos = computed(() => {
const list = [...todos.value]
switch (sortBy.value) {
case 'priority':
const priorityMap = { high: 3, medium: 2, low: 1 }
return list.sort((a, b) => priorityMap[b.priority] - priorityMap[a.priority])
case 'date':
return list.sort((a, b) => new Date(a.date) - new Date(b.date))
default:
return list
}
})
</script>
<style scoped>
.todo-list {
position: relative;
}
.todo-item {
padding: 12px;
margin: 8px 0;
border-radius: 6px;
background: #f5f5f5;
display: flex;
justify-content: space-between;
align-items: center;
}
.priority-high { border-left: 4px solid #f44336; }
.priority-medium { border-left: 4px solid #ff9800; }
.priority-low { border-left: 4px solid #4caf50; }
.todo-enter-active,
.todo-leave-active {
transition: all 0.4s ease;
}
.todo-enter-from,
.todo-leave-to {
opacity: 0;
transform: scale(0.9);
}
.todo-leave-active {
position: absolute;
width: 100%;
}
.todo-move {
transition: transform 0.4s ease;
}
</style>
四、JavaScript 钩子
4.1 完整的钩子函数
当 CSS 动画无法满足需求时,可以使用 JavaScript 钩子进行精细控制:
<template>
<Transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
:css="false"
>
<div v-if="show" class="js-animated-box">
JavaScript 动画控制
</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
// 进入前
const beforeEnter = (el) => {
el.style.opacity = '0'
el.style.transform = 'scale(0)'
}
// 进入中
const enter = (el, done) => {
// 使用 Web Animations API
const animation = el.animate([
{ opacity: 0, transform: 'scale(0)' },
{ opacity: 1, transform: 'scale(1)' }
], {
duration: 500,
easing: 'ease-out'
})
animation.onfinish = () => done()
}
// 进入完成
const afterEnter = (el) => {
console.log('进入动画完成')
}
// 进入取消
const enterCancelled = (el) => {
console.log('进入动画被取消')
}
// 离开前
const beforeLeave = (el) => {
el.style.opacity = '1'
}
// 离开中
const leave = (el, done) => {
const animation = el.animate([
{ opacity: 1, transform: 'scale(1)' },
{ opacity: 0, transform: 'scale(0)' }
], {
duration: 300,
easing: 'ease-in'
})
animation.onfinish = () => done()
}
// 离开完成
const afterLeave = (el) => {
console.log('离开动画完成')
}
// 离开取消
const leaveCancelled = (el) => {
console.log('离开动画被取消')
}
</script>
4.2 与 GSAP 集成
GSAP 是一个强大的 JavaScript 动画库,与 Vue 的过渡系统配合使用效果极佳:
<template>
<Transition
@enter="onEnter"
@leave="onLeave"
:css="false"
appear
>
<div v-if="show" class="gsap-box">
GSAP 动画
</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
import gsap from 'gsap'
const show = ref(true)
const onEnter = (el, done) => {
gsap.fromTo(el,
{ opacity: 0, y: 50, scale: 0.8 },
{
opacity: 1,
y: 0,
scale: 1,
duration: 0.6,
ease: 'back.out(1.7)',
onComplete: done
}
)
}
const onLeave = (el, done) => {
gsap.to(el, {
opacity: 0,
y: -50,
scale: 0.8,
duration: 0.4,
ease: 'power2.in',
onComplete: done
})
}
</script>
<style scoped>
.gsap-box {
padding: 40px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
text-align: center;
font-size: 18px;
}
</style>
五、性能优化
5.1 GPU 加速
使用 transform 和 opacity 属性触发 GPU 加速,避免引起重排(reflow):
/* 推荐:使用 transform 和 opacity */
.slide-enter-active {
transition: transform 0.3s ease, opacity 0.3s ease;
}
.slide-enter-from {
transform: translateX(100%);
opacity: 0;
}
/* 避免:使用 width、height、top、left 等属性 */
/* 这些属性会引起重排,性能较差 */
.bad-example-enter-active {
transition: width 0.3s ease; /* 不推荐 */
}
5.2 will-change 属性
对于复杂的动画,可以预先告知浏览器哪些属性将发生变化:
.animated-element {
will-change: transform, opacity;
}
/* 动画完成后移除 will-change */
.animated-element.animation-complete {
will-change: auto;
}
注意:will-change 会占用额外的内存,动画结束后应及时移除。
5.3 减少同时动画的元素数量
当列表中有大量元素需要同时动画时,考虑分批处理或使用虚拟列表:
<script setup>
import { ref, computed } from 'vue'
const items = ref([...Array(100).keys()])
// 分批显示动画
const visibleItems = computed(() => {
// 只显示前 20 个,或根据滚动位置动态计算
return items.value.slice(0, 20)
})
</script>
六、Vue2 vs Vue3 过渡对比
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 进入开始类名 | v-enter |
v-enter-from |
| 离开开始类名 | v-leave |
v-leave-from |
| 进入结束类名 | v-enter-to |
v-enter-to |
| 离开结束类名 | v-leave-to |
v-leave-to |
| TransitionGroup 标签 | 默认 span |
默认 span |
| TransitionGroup tag 属性 | 支持 | 支持 |
| appear 属性 | 支持 | 支持 |
| mode 属性 | in-out / out-in |
in-out / out-in |
| CSS 钩子 | 支持 | 支持 |
| JS 钩子 | 支持 | 支持,增加了 onLeaveCancelled |
| 类型支持 | 有限 | 完整的 TypeScript 支持 |
七、实战:完整的页面过渡系统
<!-- App.vue -->
<template>
<div id="app">
<nav>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
<RouterLink to="/list">列表</RouterLink>
</nav>
<RouterView v-slot="{ Component }">
<Transition name="page" mode="out-in">
<component :is="Component" />
</Transition>
</RouterView>
</div>
</template>
<style>
/* 页面过渡 */
.page-enter-active,
.page-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.page-enter-from {
opacity: 0;
transform: translateX(20px);
}
.page-leave-to {
opacity: 0;
transform: translateX(-20px);
}
/* 路由激活状态 */
.router-link-active {
color: #42b983;
font-weight: bold;
}
</style>
八、常见问题
Q1:为什么我的过渡动画不生效?
常见原因:
- 忘记给
v-for的元素添加唯一的:key - CSS 类名写错(Vue3 使用
v-enter-from而非v-enter) - 过渡元素没有条件渲染(
v-if或v-show) - CSS 选择器优先级不够
Q2:TransitionGroup 的 move 动画不生效?
确保:
- 设置了
.v-move类 - 列表项有唯一的
:key - 离开的元素设置了
position: absolute
Q3:如何在组件首次渲染时触发动画?
使用 appear 属性:
<Transition appear>
<div>首次渲染也会触发动画</div>
</Transition>
Q4:mode=“out-in” 和 mode=“in-out” 有什么区别?
out-in:当前元素先离开,新元素再进入(推荐,避免两个元素同时存在)in-out:新元素先进入,当前元素再离开
九、总结
Vue3 的过渡系统功能强大且易于使用:
| 组件 | 用途 | 关键特性 |
|---|---|---|
| Transition | 单元素/组件过渡 | 进入/离开类名、JS 钩子、mode |
| TransitionGroup | 列表过渡 | move 动画、tag 属性、FLIP |
核心要点:
- Vue3 将
v-enter改为v-enter-from,v-leave改为v-leave-from - 优先使用
transform和opacity实现 GPU 加速 - TransitionGroup 需要唯一的
:key和.v-move类 - JavaScript 钩子适合与 GSAP 等库集成
- 合理使用
will-change和mode属性优化体验
十、练习题
-
实现一个手风琴(Accordion)组件,使用 Transition 实现展开/收起的平滑动画。
-
创建一个可拖拽排序的列表,使用 TransitionGroup 实现拖拽时的位置交换动画。
-
使用 GSAP 实现一个复杂的页面加载动画序列:Logo 先放大出现,然后标题从下方滑入,最后按钮淡入。
更多推荐

所有评论(0)