Vue3 动画与过渡Vue3 的 Transition 和 TransitionGroup 组件让界面交互更加生动从 CSS 类名变化到 JavaScript 钩子全面掌握动画系统的每一个细节。一、前言动画不仅是视觉装饰更是提升用户体验的重要手段。良好的过渡动画可以帮助用户理解界面状态的变化缓解等待焦虑加载动画引导用户注意力增强界面的专业感和精致度Vue3 继承了 Vue2 的过渡系统并进行了优化和调整。本文将系统讲解 Vue3 中的动画与过渡包括基础用法、JavaScript 钩子、列表动画以及与第三方库的集成。二、Transition 组件2.1 基本用法Transition是 Vue3 内置的过渡组件包裹元素或组件后在插入、更新或移除 DOM 时自动应用过渡类名。template button clickshow !show切换显示/button Transition p v-ifshow classgreeting你好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; } /style2.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-classanimate__animated animate__bounceIn leave-active-classanimate__animated animate__bounceOut p v-ifshow classanimate__animatedAnimate.css 动画/p /Transition /template script setup import { ref } from vue const show ref(true) /script style /* 引入 Animate.css */ import animate.css; /style2.4 命名过渡通过name属性可以自定义过渡类名的前缀template Transition namefade p v-ifshow命名过渡/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; } /style2.5 CSS 过渡 vs CSS 动画Vue 的过渡系统同时支持 CSS 过渡transition和 CSS 动画animationstyle 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 clickaddItem添加项目/button button clickshuffle打乱顺序/button TransitionGroup namelist tagul li v-foritem in items :keyitem.id {{ item.text }} button clickremoveItem(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; } /style3.2 FLIP 动画原理TransitionGroup 内部使用了FLIP动画技术First记录元素的初始位置Last记录元素的最终位置Invert计算并应用反向偏移Play播放过渡动画记录 First记录 LastInvertPlay元素初始位置计算最终位置应用反向偏移触发过渡动画元素平滑移动到新位置TransitionGroup 会自动处理这个过程开发者只需定义.v-move类即可。3.3 列表排序动画template div classtodo-app div classfilters button clicksortBy default默认/button button clicksortBy priority按优先级/button button clicksortBy date按日期/button /div TransitionGroup nametodo tagdiv classtodo-list div v-fortodo in sortedTodos :keytodo.id classtodo-item :classpriority-${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-enterbeforeEnter enterenter after-enterafterEnter enter-cancelledenterCancelled before-leavebeforeLeave leaveleave after-leaveafterLeave leave-cancelledleaveCancelled :cssfalse div v-ifshow classjs-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(离开动画被取消) } /script4.2 与 GSAP 集成GSAP 是一个强大的 JavaScript 动画库与 Vue 的过渡系统配合使用效果极佳template Transition enteronEnter leaveonLeave :cssfalse appear div v-ifshow classgsap-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 过渡对比特性Vue2Vue3进入开始类名v-enterv-enter-from离开开始类名v-leavev-leave-from进入结束类名v-enter-tov-enter-to离开结束类名v-leave-tov-leave-toTransitionGroup 标签默认span默认spanTransitionGroup tag 属性支持支持appear 属性支持支持mode 属性in-out/out-inin-out/out-inCSS 钩子支持支持JS 钩子支持支持增加了onLeaveCancelled类型支持有限完整的 TypeScript 支持七、实战完整的页面过渡系统!-- App.vue -- template div idapp nav RouterLink to/首页/RouterLink RouterLink to/about关于/RouterLink RouterLink to/list列表/RouterLink /nav RouterView v-slot{ Component } Transition namepage modeout-in component :isComponent / /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的元素添加唯一的:keyCSS 类名写错Vue3 使用v-enter-from而非v-enter过渡元素没有条件渲染v-if或v-showCSS 选择器优先级不够Q2TransitionGroup 的 move 动画不生效确保设置了.v-move类列表项有唯一的:key离开的元素设置了position: absoluteQ3如何在组件首次渲染时触发动画使用appear属性Transition appear div首次渲染也会触发动画/div /TransitionQ4mode“out-in” 和 mode“in-out” 有什么区别out-in当前元素先离开新元素再进入推荐避免两个元素同时存在in-out新元素先进入当前元素再离开九、总结Vue3 的过渡系统功能强大且易于使用组件用途关键特性Transition单元素/组件过渡进入/离开类名、JS 钩子、modeTransitionGroup列表过渡move 动画、tag 属性、FLIP核心要点Vue3 将v-enter改为v-enter-fromv-leave改为v-leave-from优先使用transform和opacity实现 GPU 加速TransitionGroup 需要唯一的:key和.v-move类JavaScript 钩子适合与 GSAP 等库集成合理使用will-change和mode属性优化体验十、练习题实现一个手风琴Accordion组件使用 Transition 实现展开/收起的平滑动画。创建一个可拖拽排序的列表使用 TransitionGroup 实现拖拽时的位置交换动画。使用 GSAP 实现一个复杂的页面加载动画序列Logo 先放大出现然后标题从下方滑入最后按钮淡入。