13-Vue3 动画与过渡
2026/7/1 12:56:46 网站建设 项目流程

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-enterv-enter-from元素插入前的初始状态
进入中v-enter-activev-enter-active整个进入过渡阶段
进入结束v-enter-tov-enter-to元素插入后的最终状态
离开开始v-leavev-leave-from元素移除前的初始状态
离开中v-leave-activev-leave-active整个离开过渡阶段
离开结束v-leave-tov-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:播放过渡动画

记录 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 加速

使用transformopacity属性触发 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 过渡对比

特性Vue2Vue3
进入开始类名v-enterv-enter-from
离开开始类名v-leavev-leave-from
进入结束类名v-enter-tov-enter-to
离开结束类名v-leave-tov-leave-to
TransitionGroup 标签默认span默认span
TransitionGroup tag 属性支持支持
appear 属性支持支持
mode 属性in-out/out-inin-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:为什么我的过渡动画不生效?

常见原因:

  1. 忘记给v-for的元素添加唯一的:key
  2. CSS 类名写错(Vue3 使用v-enter-from而非v-enter
  3. 过渡元素没有条件渲染(v-ifv-show
  4. CSS 选择器优先级不够

Q2:TransitionGroup 的 move 动画不生效?

确保:

  1. 设置了.v-move
  2. 列表项有唯一的:key
  3. 离开的元素设置了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

核心要点

  1. Vue3 将v-enter改为v-enter-fromv-leave改为v-leave-from
  2. 优先使用transformopacity实现 GPU 加速
  3. TransitionGroup 需要唯一的:key.v-move
  4. JavaScript 钩子适合与 GSAP 等库集成
  5. 合理使用will-changemode属性优化体验

十、练习题

  1. 实现一个手风琴(Accordion)组件,使用 Transition 实现展开/收起的平滑动画。

  2. 创建一个可拖拽排序的列表,使用 TransitionGroup 实现拖拽时的位置交换动画。

  3. 使用 GSAP 实现一个复杂的页面加载动画序列:Logo 先放大出现,然后标题从下方滑入,最后按钮淡入。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询