Vue3动画怎么实现 新手常用方法有哪些坑
很多刚学Vue3的前端新手都会问,动画明明CSS3就能写,为什么还要用Vue3的内置组件或者组合式API?其实CSS3处理简单的入场退场、 hover效果没问题,但遇到列表元素动态排序、组件切换过渡链、复杂的基于状态的连续动画这些场景,纯CSS3就有点力不从心了——要么得手动加一堆class、定时器,要么代码耦合度高,维护起来头疼,Vue3刚好把这些复杂场景的动画逻辑封装好了,只要按规则用,就能写出优雅、易维护的动画。
先从最简单的单元素/组件过渡讲起
过渡的核心是什么?
Vue3的单元素/组件过渡,本质是在元素进入、离开DOM的特定时机自动添加/删除预设的class,当你用<transition>包裹一个带v-if、v-show、is动态组件切换的元素时,Vue会在这个过程中触发6个关键的时间点,给它加对应的过渡类名。
那6个类名是什么?记不住怎么办?
不用死记硬背所有细节,先把基础的3组核心概念搞清楚就行:进入(enter)和离开(leave)、起始(start)、激活(active)、结束(to),简单拆解的话,每组过程(enter/leave)都是从“加起始类→激活动画→删起始类加结束类→动画完成后删结束类和激活类”这么个流程。
举个例子,用自定义过渡名fade的话,对应的类名就是fade-enter(进入前状态)、fade-enter-active(进入动画激活,整个过程都存在,用来定义transition属性)、fade-enter-to(进入后状态);离开类名是一样的逻辑,把enter换成leave就行。
如果没给过渡名,默认是v-enter这些前缀。
能不能写个最基础的淡入淡出例子?
当然可以,就拿一个带v-if的按钮切换的div来说:
<template>
<button @click="isShow = !isShow">切换淡入淡出效果</button>
<!-- 这里用自定义过渡名fade -->
<transition name="fade">
<div v-if="isShow" class="box">我是淡入淡出的内容</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const isShow = ref(true)
</script>
<style scoped>
.box {
width: 200px;
height: 100px;
background-color: #42b983;
display: flex;
align-items: center;
justify-content: center;
color: white;
border-radius: 8px;
}
/* 进入激活和离开激活都要加transition属性,因为入场退场都是用这个类控制动画时长、曲线 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease-in-out;
}
/* 淡入前透明,淡入后不透明 */
.fade-enter-from {
opacity: 0;
}
/* 注意哦!Vue3把Vue2的v-enter和v-leave改成了v-enter-from和v-leave-from!很多新手会踩这个坑! */
.fade-leave-to {
opacity: 0;
}
</style>
对了,刚才提了Vue2到Vue3的第一个大坑:过渡类名的前缀变了!Vue2里进入前是v-enter,离开前是v-leave,Vue3统一加了-from后缀,变成v-enter-from、v-leave-from了,很多新手直接抄Vue2的代码,结果动画没反应,找半天bug才发现这里改了。
什么时候用CSS动画代替CSS过渡?
刚才的淡入淡出是“从一个状态到另一个状态”的简单变化,用CSS transition没问题,但如果是需要多帧循环的动画(比如抖动、旋转、弹跳入场),就得用CSS keyframes动画了。
CSS动画和过渡的类名使用有啥不一样?
CSS transition类名里的*-active主要用来加transition属性,而CSS keyframes里的*-active主要用来绑定animation属性(包括动画名、时长、次数这些);起始类*-enter-from和结束类*-leave-to一般可以不用写,因为keyframes里已经定义好了起始和结束帧的状态。
写个弹跳入场的例子试试?
比如一个表单提交成功的提示框,点击显示时弹跳入场,点击关闭时直接消失:
<template>
<button @click="showToast = !showToast">显示成功提示</button>
<transition name="bounce">
<div v-if="showToast" class="toast" @click="showToast = false">🎉 提交成功!</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const showToast = ref(false)
</script>
<style scoped>
.toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
background-color: #333;
color: white;
border-radius: 20px;
font-size: 16px;
z-index: 9999;
}
.bounce-enter-active {
animation: bounce-in 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* 离开时直接透明度变0,不用弹跳 */
.bounce-leave-active {
transition: opacity 0.3s;
}
.bounce-leave-to {
opacity: 0;
}
@keyframes bounce-in {
0% {
transform: translateX(-50%) translateY(-100%);
opacity: 0;
}
50% {
transform: translateX(-50%) translateY(20px);
opacity: 1;
}
70% {
transform: translateX(-50%) translateY(-10px);
}
100% {
transform: translateX(-50%) translateY(0);
}
}
</style>
这里用到了cubic-bezier贝塞尔曲线,让弹跳更自然,新手可以直接用预设的ease-out-bounce这些值吗?Vue3内置组件默认没有预设这些,但可以自己封装成工具类,或者用一些第三方动画库。
刚才说的第三方动画库,Vue3推荐用啥?
首推Animate.css!它是一个纯CSS的、免费开源的动画库,里面有上百种现成的动画效果,比如flip、zoomIn、slideOutLeft这些,不用自己写keyframes,直接拿过来用就行。
Animate.css和Vue3的怎么结合?
结合的时候要注意两个属性:enter-active-class和leave-active-class,这两个属性可以覆盖默认的*-enter-active和*-leave-active类名,直接绑定Animate.css提供的动画激活类(Animate.css的激活类是animate__animated,动画类是animate__zoomIn这种)。
有没有具体的结合例子?
没问题,还是刚才的提示框,换成Animate.css的zoomInDown和zoomOutUp动画:
<template>
<!-- 先引入Animate.css,这里用CDN,也可以npm install animate.css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<button @click="showToast = !showToast">显示Animate.css提示</button>
<transition
name="animate__animated"
enter-active-class="animate__animated animate__zoomInDown"
leave-active-class="animate__animated animate__zoomOutUp"
>
<div v-if="showToast" class="toast" @click="showToast = false">✨ 使用Animate.css超简单!</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const showToast = ref(false)
</script>
<style scoped>
/* toast的样式和之前一样,这里就不重复写了 */
.toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
background-color: #ff6b6b;
color: white;
border-radius: 20px;
font-size: 16px;
z-index: 9999;
cursor: pointer;
}
</style>
这里有个新手容易忽略的细节:不管是<transition>还是后面要讲的<transition-group>,结合Animate.css的时候,必须同时加animate__animated这个激活类,不然动画不会生效!因为Animate.css的所有动画类,都必须在animate__animated的包裹下才有作用——它给元素加了animation-fill-mode: both这些基础属性,控制动画播放前后的状态。
列表元素要排序、要增删动画怎么办?
刚才的<transition>只能处理单个元素/组件的过渡,如果是列表(比如v-for渲染的数组),就得用<transition-group>了。
和有啥区别?
主要有3个区别:
- 渲染方式:
<transition>本身不会渲染任何DOM元素,只是作为过渡的“容器”;<transition-group>默认会渲染成一个<span>元素,不过可以用tag属性改成其他标签,比如ul、div。 - 适用场景:
<transition>处理单个或多个互斥的元素/组件(比如用v-if和v-else-if的几个div,同一时间只能显示一个);<transition-group>处理多个同时存在、可以动态增删或排序的元素(比如v-for的数组元素,同一时间可能显示多个)。 - 新增的过渡类名:
<transition-group>除了有<transition>的6个enter/leave类名,还有一个*-move类名,用来处理列表元素移动时的过渡(比如排序、删除中间元素后,后面的元素往前挪的过程)。举个带增删和排序的列表动画例子
比如一个待办事项列表,点击添加按钮加事项,点击事项删除它,点击排序按钮按字母顺序排序,所有操作都有动画:
<template> <div class="todo-container"> <h2>待办事项动画列表</h2> <div class="input-group"> <input type="text" v-model="newTodo" placeholder="输入待办事项" @keyup.enter="addTodo"> <button @click="addTodo">添加</button> <button @click="sortTodos">按字母排序</button> </div> <!-- transition-group默认渲染span,这里改成ul --> <transition-group name="todo-list" tag="ul" class="todo-list"> <!-- 每个列表项必须有唯一的key,不能用index!这是transition-group最大的坑! --> <li v-for="todo in todos" :key="todo.id" @click="removeTodo(todo.id)" class="todo-item"> {{ todo.content }} </li> </transition-group> </div> </template>
除了CSS类名,还有没有更灵活的方式?比如用JS控制动画
有的!Vue3的<transition>和<transition-group>都支持JS钩子函数,这些钩子函数可以让你用原生JS、GSAP、Velocity.js等第三方JS动画库来控制动画,适合处理非常复杂的、基于状态的连续动画(比如数字滚动、SVG路径动画、复杂的物理动画)。
JS钩子函数有哪些?
JS钩子函数和CSS过渡的时间点是一一对应的,主要有:
@before-enter:进入前触发,对应CSS的*-enter-from类名被添加之前@enter:进入触发,对应CSS的*-enter-from被删除、*-enter-to被添加的时机,这里必须传入一个done回调函数,动画完成后调用done()告诉Vue动画结束了@after-enter:进入完成后触发@enter-cancelled:进入动画被取消时触发(比如在动画还没完成时就切换了v-if/v-show)- 离开的钩子函数是一样的逻辑:
@before-leave、@leave(必须传done)、@after-leave、@leave-cancelled - 如果是
<transition-group>,还有移动相关的钩子:@before-move、@move、@after-move用GSAP写个数字滚动的JS钩子动画例子
GSAP是一个非常强大的JS动画库,性能比CSS动画还好,尤其是处理复杂动画的时候,先npm install gsap,然后写一个数字从0滚动到目标值的组件:
<template> <div class="counter-container"> <h2>GSAP数字滚动动画</h2> <div class="counter">当前业绩:¥<span ref="numberSpan">0</span></div> <button @click="isShowCounter = !isShowCounter">{{ isShowCounter ? '重置数字' : '显示数字' }}</button> </div> </template>
JS钩子结合GSAP的过渡动画
有没有什么通用的Vue3动画优化技巧?
当然有,优化动画可以让网页更流畅,尤其是在手机等性能较弱的设备上:
- 尽量使用transform和opacity做动画:这两个属性不会触发浏览器的重排(reflow),只会触发重绘(repaint)和合成(composite),性能最好,尽量不要用width、height、margin、padding、top、left这些属性做动画,因为它们会触发重排,导致整个页面的布局重新计算,非常消耗性能。
- 开启GPU加速:可以给做动画的元素加
transform: translateZ(0)、transform: rotate3d(0,0,0,0)或者will-change: transform, opacity,让浏览器把这个元素单独放到一个合成层,用GPU来渲染,提升性能,不过要注意不要滥用will-change,不然会占用太多GPU内存,反而适得其反——一般只给即将做动画的元素加,动画完成后可以去掉。 - 合理控制动画时长:一般入场退场动画的时长控制在0.3s-0.8s之间,太长会让用户觉得网页慢,太短会让用户看不清动画效果。
- 动画曲线要自然:入场动画一般用ease-out或者back.out(有弹性效果),退场动画一般用ease-in或者back.in,循环动画可以用linear,新手可以用贝塞尔曲线生成工具(比如cubic-bezier.com)来调整合适的曲线。
- 避免在动画期间做复杂的计算:比如在
@enter钩子函数里做大量的DOM操作、数组遍历、网络请求,这些都会阻塞主线程,导致动画卡顿。 - 用v-show代替v-if做频繁切换的动画:因为v-if会把元素从DOM中完全删除和添加,而v-show只是切换元素的display属性,性能更好,过渡效果也更平滑。
总结一下Vue3动画的常用方法和避坑点
Vue3动画主要有3种实现方法:纯CSS过渡/动画、结合第三方CSS动画库(比如Animate.css)、结合JS钩子函数和第三方JS动画库(比如GSAP),新手刚开始可以从纯CSS过渡/动画入手,掌握基础的类名和<transition>、<transition-group>的使用;然后可以尝试结合Animate.css,快速实现各种酷炫的效果;最后如果遇到复杂的场景,再用JS钩子函数和GSAP。
最后再强调一下3个最容易踩的坑:
- Vue3的过渡类名前缀加了
-from,把v-enter改成了v-enter-from,v-leave改成了v-leave-from; <transition-group>的每个列表项必须有唯一的、稳定的key,不能用index;- 用纯JS钩子控制动画时,一定要加
css="false"属性,避免和CSS动画冲突。
希望这篇文章能帮到刚学Vue3动画的你,如果你有其他问题,欢迎在评论区留言讨论!
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


