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

Vue3动画怎么实现 新手常用方法有哪些坑

terry 13小时前 阅读数 358 #Vue
文章标签 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个区别:

  1. 渲染方式:<transition>本身不会渲染任何DOM元素,只是作为过渡的“容器”;<transition-group>默认会渲染成一个<span>元素,不过可以用tag属性改成其他标签,比如ul、div。
  2. 适用场景:<transition>处理单个或多个互斥的元素/组件(比如用v-if和v-else-if的几个div,同一时间只能显示一个);<transition-group>处理多个同时存在、可以动态增删或排序的元素(比如v-for的数组元素,同一时间可能显示多个)。
  3. 新增的过渡类名:<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>
``` 这里一定要重点说``的最大新手坑:**每个列表项的key必须是唯一的、稳定的,绝对不能用v-for的index作为key!** 为什么?因为当你增删或排序列表时,index会跟着变化,Vue就无法正确识别哪个是原来的元素,哪个是新增/删除/移动的,这样动画就会乱掉——比如删除中间的元素,后面的元素可能直接跳过来,没有move动画;排序的时候,元素的内容和索引绑定,也不会有正确的过渡,刚才的例子用了自增的idCounter作为key,实际开发中最好用后端返回的UUID或者nanoid生成的唯一字符串。

除了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>
``` 刚才的例子用了watch监听状态变化,但如果是单个组件的入场退场,其实更适合用``的JS钩子函数,比如用JS钩子函数写一个GSAP的弹跳入场动画: ```html ``` 这里又有一个新手容易踩的坑:**如果用纯JS钩子控制动画,一定要给``或``加`:css="false"`属性!** 不然Vue还是会自动添加CSS过渡类名,如果你的元素有相关的CSS transition或animation,就会和JS动画冲突,导致动画效果混乱。

有没有什么通用的Vue3动画优化技巧?

当然有,优化动画可以让网页更流畅,尤其是在手机等性能较弱的设备上:

  1. 尽量使用transform和opacity做动画:这两个属性不会触发浏览器的重排(reflow),只会触发重绘(repaint)和合成(composite),性能最好,尽量不要用width、height、margin、padding、top、left这些属性做动画,因为它们会触发重排,导致整个页面的布局重新计算,非常消耗性能。
  2. 开启GPU加速:可以给做动画的元素加transform: translateZ(0)、transform: rotate3d(0,0,0,0)或者will-change: transform, opacity,让浏览器把这个元素单独放到一个合成层,用GPU来渲染,提升性能,不过要注意不要滥用will-change,不然会占用太多GPU内存,反而适得其反——一般只给即将做动画的元素加,动画完成后可以去掉。
  3. 合理控制动画时长:一般入场退场动画的时长控制在0.3s-0.8s之间,太长会让用户觉得网页慢,太短会让用户看不清动画效果。
  4. 动画曲线要自然:入场动画一般用ease-out或者back.out(有弹性效果),退场动画一般用ease-in或者back.in,循环动画可以用linear,新手可以用贝塞尔曲线生成工具(比如cubic-bezier.com)来调整合适的曲线。
  5. 避免在动画期间做复杂的计算:比如在@enter钩子函数里做大量的DOM操作、数组遍历、网络请求,这些都会阻塞主线程,导致动画卡顿。
  6. 用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个最容易踩的坑:

  1. Vue3的过渡类名前缀加了-from,把v-enter改成了v-enter-from,v-leave改成了v-leave-from;
  2. <transition-group>的每个列表项必须有唯一的、稳定的key,不能用index;
  3. 用纯JS钩子控制动画时,一定要加css="false"属性,避免和CSS动画冲突。

希望这篇文章能帮到刚学Vue3动画的你,如果你有其他问题,欢迎在评论区留言讨论!

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门