Code前端首页关于Code前端联系我们

1.Vue3 的 Transition 到底是干啥的?

terry 2小时前 阅读数 4 #Vue
文章标签 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 里。

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

发表评论:

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

热门