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前端网发表,如需转载,请注明页面地址。
code前端网



发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。