1.Vue3 的 Transition 到底是干啥的?
现在前端项目里交互体验越来越重要,动画过渡效果能让页面更灵动,Vue3 里的 Transition 就是专门处理元素/组件进入、离开动画的工具,不少刚接触的同学会疑惑“这玩意儿咋用?能搞出啥好看的动效?” 下面通过问答形式,把 Transition 从基础到实战的知识点拆明白~
可以理解成**“给元素/组件的显示、隐藏过程加动画的「容器」”**,比如点击按钮弹出弹窗时,让弹窗从透明到不透明、从缩小到放大;关闭时反向执行动画,这些“出现→存在→消失”的过程,Transition 能帮我们优雅实现。它的核心逻辑是:在元素状态变化(v-if 切换显示隐藏、v-show 切换可见性)时,自动在不同阶段给元素添加/移除 CSS 类名,我们只需要写这些类名对应的动画样式就行,举个简单对比:没有 Transition 时,元素显示隐藏是“瞬间切换”;用了 Transition 后,就能让切换过程变成“渐变、滑动、缩放”等平滑过渡。
Vue3 里 Transition 组件是内置的(不用额外装依赖),它对单元素/单组件的过渡支持很好,要是处理列表类的多元素过渡,就得用它的“兄弟组件” TransitionGroup 啦~
单元素/组件过渡咋实现?分几步走?
想让一个按钮控制的弹窗有“淡入+缩放”动画,步骤大概这样:
步骤1:用 Transition 组件把目标元素包起来
<template> <button @click="isShow = !isShow">切换弹窗</button> <Transition> <div class="popup" v-if="isShow">我是弹窗</div> </Transition> </template>
这里注意:只有一个子元素能被 Transition 包裹,如果包了多个,Vue 会报错~
步骤2:写 CSS 过渡类名
Vue3 里 Transition 给元素自动加的类名是这几个套路:
- 进入过程(元素从隐藏→显示):
v-enter-from
(进入前的初始状态,比如透明、缩小)→v-enter-active
(进入过程的持续状态,写过渡时长、动画曲线)→v-enter-to
(进入后的最终状态,比如不透明、正常大小) - 离开过程(元素从显示→隐藏):
v-leave-from
(离开前的初始状态,即元素当前显示的状态)→v-leave-active
(离开过程的持续状态)→v-leave-to
(离开后的最终状态,比如透明、缩小)
CSS 可以这么写:
.popup { /* 元素默认样式:定位、背景这些 */ opacity: 1; transform: scale(1); } .v-enter-from, .v-leave-to { opacity: 0; transform: scale(0.8); } .v-enter-active, .v-leave-active { transition: all 0.3s ease; }
这样一来,弹窗显示时会从透明+缩小,用 0.3 秒过渡到正常;关闭时反向执行,自然又丝滑~
步骤3:用 JS 钩子搞更灵活的动画
如果不想用 CSS 过渡(比如要做复杂的序列动画、结合第三方库),可以用 Transition 提供的 JS 钩子,@enter
(进入开始时触发)、@after-enter
(进入结束后触发)等。
举个用 GSAP(GreenSock 动画库)的例子:
<Transition @enter="handleEnter" @leave="handleLeave" > <div class="popup" v-if="isShow"></div> </Transition> <script setup> import { gsap } from 'gsap' function handleEnter(el, done) { // el 是要过渡的 DOM 元素 gsap.fromTo(el, { opacity: 0, scale: 0.8 }, // 起始状态 { opacity: 1, scale: 1, duration: 0.3, onComplete: done // 动画完成后必须调用 done,告诉 Vue 过渡结束 } ) } function handleLeave(el, done) { gsap.to(el, { opacity: 0, scale: 0.8, duration: 0.3, onComplete: done }) } </script>
这种方式自由度更高,适合做 CSS 搞不定的复杂动画~
列表元素过渡用啥?TransitionGroup 咋玩?
如果要做“todo 列表添加/删除项时,每个项都有滑动+渐变”这种多元素过渡,就得用 TransitionGroup
组件,它和 Transition 的区别是:
- TransitionGroup 可以包裹多个子元素,但每个子元素必须加唯一的
key
(和 v-for 里的 key 同理); - 列表过渡时,除了“进入/离开”动画,还能处理元素位置变化的过渡(比如列表项排序后,元素滑动到新位置),这时候会自动添加
v-move
类名,用来控制移动过程的过渡。
举个 todo 列表的例子:
<template> <input v-model="newTodo" placeholder="新增任务" /> <button @click="addTodo">添加</button> <TransitionGroup tag="ul" name="todo"> <li v-for="(todo, index) in todoList" :key="todo.id" @click="removeTodo(index)" > {{ todo.text }} </li> </TransitionGroup> </template> <style> /* 进入/离开动画 */ .todo-enter-from, .todo-leave-to { opacity: 0; transform: translateY(20px); } .todo-enter-active, .todo-leave-active { transition: all 0.3s ease; } /* 元素移动时的过渡(比如删除中间项,后面项往上滑) */ .todo-move { transition: transform 0.3s ease; } </style>
这里 tag="ul"
是指定 TransitionGroup 渲染成 <ul>
标签(默认不渲染标签,会导致结构有问题,所以必须指定 tag),添加新 todo 时,新项会从下往上滑入;删除项时,被删项渐变消失,后面的项会平滑“补位”滑动,体验很流畅~
能和 Animate.css、GSAP 这些第三方库结合不?
必须能!而且玩法不止一种:
玩法1:CSS 类名直接蹭 Animate.css 的预定义动画
Animate.css 提供了上百种现成的动画类(bounceIn
fadeInUp
),我们可以把这些类名丢给 Transition 的 enter-from-class
enter-active-class
等属性,不用自己写 CSS 过渡。
例子:让弹窗用 Animate.css 的 bounceIn 动画进入,bounceOut 动画离开
<Transition name="custom" enter-from-class="animate__opacity-0 animate__scale-0_8" enter-active-class="animate__animated animate__bounceIn" leave-active-class="animate__animated animate__bounceOut" > <div class="popup" v-if="isShow"></div> </Transition>
注意要先在项目里引入 Animate.css(npm 装了后 import 或者 CDN 引入),然后类名用它的前缀 animate__
~
玩法2:JS 钩子结合 GSAP 做复杂动画
GSAP 能做很多 CSS 搞不定的事儿,元素沿着贝塞尔曲线飞入场、带弹性的缩放”,这时候用 Transition 的 @enter
@leave
钩子,在函数里调用 GSAP 的 API 就行,前面单元素过渡里已经举过例子啦~
甚至可以做“ staggered 动画”(比如列表项逐个延迟入场),用 GSAP 的 stagger
方法:
<TransitionGroup @enter="handleListEnter" > <li v-for="item in list" :key="item.id">{{ item.name }}</li> </TransitionGroup> <script setup> import { gsap } from 'gsap' function handleListEnter(el, done, index) { // index 是元素在列表中的位置,用来做延迟 gsap.fromTo(el, { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 0.5, delay: index * 0.1, // 每个元素延迟 0.1 秒,实现逐个入场 onComplete: done } ) } </script>
这种列表逐个滑入的效果,在后台管理系统的表格加载、首页模块入场时特别常用,氛围感直接拉满~
过渡动画性能咋优化?移动端容易卡咋办?
动画卡不卡,核心看浏览器渲染压力,尤其是移动端(性能比桌面端弱),得注意这几点:
① 能用 CSS 过渡就不用 JS 动画
CSS 动画由浏览器渲染进程的合成线程处理,能利用硬件加速;JS 动画要经过 JS 引擎→渲染线程,步骤更多,容易掉帧,所以简单的渐变、缩放、滑动,优先用 CSS 写在 v-enter-active
v-leave-active
里。
② 用 transform
和 opacity
代替 top/left
修改 top
left
会触发页面重排(Layout),而 transform
只触发重绘(Paint)甚至合成(Composite),性能好很多,元素从下往上滑”,用 transform: translateY(100%)
代替 top: 100px
。
③ 给动画元素加硬件加速
可以在 CSS 里加 will-change: transform
(告诉浏览器我要动 transform 啦,提前准备),或者 transform: translateZ(0)
(强制触发 GPU 加速)。
.v-enter-active, .v-leave-active { will-change: transform, opacity; transform: translateZ(0); transition: all 0.3s ease; }
④ 列表过渡别瞎用 key
TransitionGroup 里的子元素 key 要稳定,别用索引(index)当 key!todo 列表用 todo.id 当 key,而不是 index,因为索引当 key,删除一项后,后面所有项的 key 都会变,导致 TransitionGroup 以为所有项都被删除又重新创建,动画会乱套,性能也爆炸。
实际项目里,Transition 能搞哪些炫酷场景?
说几个常见又吸睛的案例,学会了能让项目质感起飞:
场景1:路由切换过渡(页面切换动画)
给不同页面加“淡入淡出”“左右滑动”过渡,用户体验瞬间像原生 App,做法是:在 VueRouter 的路由出口(<router-view>
)外面包一层 Transition,根据路由元信息(meta)动态切换过渡动画。
例子:
<template> <Transition :name="transitionName"> <router-view></router-view> </Transition> </template> <script setup> import { watch } from 'vue' import { useRoute } from 'vue-router' const route = useRoute() let transitionName = 'fade' watch(route, (newRoute, oldRoute) => { // 根据路由 meta 里的 transition 指定动画 transitionName = newRoute.meta.transition || 'fade' }) </script> <style> .fade-enter-from, .fade-leave-to { opacity: 0; } .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .slide-left-enter-from { transform: translateX(100%); } .slide-left-enter-active { transition: transform 0.5s; } /* 其他方向的滑动动画同理 */ </style>
然后在路由配置里给页面加 meta:
const routes = [ { path: '/home', component: Home, meta: { transition: 'slide-left' } }, { path: '/about', component: About, meta: { transition: 'fade' } } ]
这样不同页面切换时,动画能根据需求定制,比生硬的页面跳转高级多了~
场景2:弹窗/抽屉的滑入滑出
移动端很常见的“底部弹窗从下往上滑入,关闭时滑出”,用 Transition + v-show 就能实现:
<Transition name="slide-bottom"> <div class="dialog" v-show="isShow"> <div class="dialog-content">内容</div> </div> </Transition> <style> .dialog { position: fixed; bottom: 0; width: 100%; background: #fff; } .slide-bottom-enter-from, .slide-bottom-leave-to { transform: translateY(100%); } .slide-bottom-enter-active, .slide-bottom-leave-active { transition: transform 0.3s; } </style>
抽屉组件(从右侧滑入)同理,把 translateY
换成 translateX(100%)
就行~
场景3:加载时的骨架屏渐变
列表加载前显示骨架屏,骨架屏的每个“占位块”逐个淡入,用 TransitionGroup + 延迟动画:
<TransitionGroup tag="div" name="skeleton" class="skeleton-list"> <div v-for="i in 5" :key="i" class="skeleton-item" :style="{ animationDelay: i * 0.1 + 's' }" ></div> </TransitionGroup> <style> .skeleton-item { width: 80%; height: 20px; background: #eee; margin: 10px 0; border-radius: 4px; } .skeleton-enter-from { opacity: 0; } .skeleton-enter-active { transition: opacity 0.3s; animation: pulse 1.5s infinite; /* pulse 是骨架屏的渐变闪烁动画 */ } @keyframes pulse { 0% { background-color: #eee; } 50% { background-color: #ddd; } 100% { background-color: #eee; } } </style>
这种渐变+闪烁的骨架屏,比干巴巴的“加载中”文字友好太多~
用 Transition 时最容易踩的坑是啥?咋避?
总结几个高频踩坑点,避坑=省时间:
坑1:单元素过渡,没把元素包在 Transition 里
比如直接写 <div v-if="xxx">
不加 Transition,那动画肯定没效果。必须用 <Transition>
把要过渡的元素单独包起来,而且只能包一个元素~
坑2:列表过渡用了 Transition 而非 TransitionGroup
如果是 v-for 循环的列表,用 Transition 包裹会报错,因为 Transition 不支持多子元素。列表过渡必须用 TransitionGroup,还要给每个子元素加唯一 key~
坑3:CSS 类名写错(Vue3 和 Vue2 类名变化)
Vue2 里是 v-enter
v-leave
,Vue3 改成了 v-enter-from
v-leave-from
(因为更直观:from 表示“从什么状态开始”),如果按照 Vue2 的写法,动画肯定不生效,得注意版本差异~
坑4:JS 钩子没调用 done 回调
用 @enter
@leave
这些 JS 钩子时,动画完成后必须调用 done()
告诉 Vue 过渡结束,否则 Vue 会以为过渡还没完成,导致元素一直不消失或者状态异常~
坑5:TransitionGroup 没指定 tag 属性
TransitionGroup 默认不会渲染外层标签,直接渲染子元素列表,这会导致 DOM 结构缺失(比如列表项外面没 ul/ol 标签),还可能引发样式问题。必须给 TransitionGroup 加 tag 属性,tag="ul"
,让它渲染成指定标签~
看完这些问答,是不是对 Vue3 Transition 咋用、能玩出啥花样更清晰了?其实核心就是“用 Transition/TransitionGroup 包元素,通过 CSS 类或 JS 钩子控制动画阶段”,从简单的淡入淡出,到复杂的列表交互动画,只要把这些基础逻辑吃透,再结合实际场景拓展,就能让页面动效既丝滑又有创意~ 下次做项目时,别再让元素“生硬闪现”啦,用 Transition 给用户来点视觉惊喜~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。