阅读 754

Vue 3 动效

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战


系列文章


本文介绍 Vue 3 的元素进入(显示)/离开(隐藏)和列表(重排)的过渡动效实现方法,主要针对 🎉 与 Vue 2 的不同点

Vue 支持的过渡动效主要是针对元素素进入(显示)/离开(隐藏)和列表(重排)的情况,基于 CSS3 的 transitionanimation 属性实现的。

💡 对于其他过渡动效,以及在 web 上创建流畅的动画所需考虑的性能因素,可以参考官方文章这一章

Vue 中常见的触发动画的情况:

  • 条件渲染 v-ifv-show 切换,动态组件属性 is 切换
  • 列表更新
  • 状态更新

元素进入/离开过渡

Vue 提供内置组件 <transition name="animationType"></transition> 作为容器,然后就可以有多种方法为包裹在其中的(直接子元素)元素设置进入/离开时的动画:

  • 自动为发生过渡的元素添加特定的 class(并在结束后移除相应的 class 属性),可以基于这些 class 属性使用 CSS 实现动画。而且可以定制 class 的属性值,便于集成第三方 CSS 动画库,如 animate.css
  • 在发生过渡同时触发相应的过渡钩子函数,便于通过 JS 实现动画,也可以集成第三方 JavaScript 动画库

CSS 过渡

Vue 会在过渡的不同阶段,为元素插入 6 种不同后缀的 class 属性,可以使用这些 class 作为选择器,更精细地设置元素的在不同过渡阶段的样式:

transitions.svg

  • 🎉 v-enter-from 定义进入过渡的开始状态,在元素被插入之前生效,在元素被插入之后的下一帧移除。
  • v-enter-active 在整个进入过渡的阶段中应用,在过渡/动画完成之后移除。可以使用这个类名作为 CSS 选择器设置过渡效果,例如通过设置 CSS 属性 transition 定义进入过渡的过程时间,延迟或曲线函数。
  • v-enter-to 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
  • 🎉 v-leave-from 定义离开过渡的开始状态,在离开过渡被触发时立刻生效,下一帧被移除。
  • v-leave-active 在整个离开过渡的阶段中应用,在过渡/动画完成之后移除。可以使用这个类名作为 CSS 选择器设置过渡效果,例如通过设置 CSS 属性 transition 定义离开过渡的过程时间,延迟和曲线函数。
  • v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被删除),在过渡/动画完成之后移除。

💡 以上 6 种不同后缀的 class 属性都使用了 v- 作为默认前缀,如果容器标签设置了属性 name<transition name="animationType">,则动态插入的 class 属性名称就会以 animationType- 为前缀,即 v-enter 会替换为 animationType-enter,记得设置样式时使用相应的前缀,而不是默认前缀 v-

<div id="demo">
  <button @click="show = !show">
    Toggle show
  </button>

  <transition name="slide-fade">
    <p v-if="show">hello</p>
  </transition>
</div>
复制代码
const Demo = {
  data() {
    return {
      show: true
    }
  }
}

Vue.createApp(Demo).mount('#demo')
复制代码
/* 可以设置不同的进入和离开动画   */
/* 设置持续时间和动画函数        */
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}
复制代码

如果要用第三方的 CSS 动画库,如 Animate.css,一般需要在 DOM 元素上插入特定的类名来应用相应的 CSS 样式,可以在标签 <transition></transition> 上使用如下列出的 attribute,这样 Vue 会在过渡的相应阶段为容器内的变更的(直接子元素)元素添加相应的自定义过渡类名

  • 🎉 enter-from-class="custom-class-name" 在元素进入页面前添加的类名
  • enter-active-class="custom-class-name"
  • enter-to-class="custom-class-name"
  • 🎉 leave-from-class="custom-class-name"
  • leave-active-class="custom-class-name"
  • leave-to-class="custom-class-name"
<link
  href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.0/animate.min.css"
  rel="stylesheet"
  type="text/css"
/>

<div id="demo">
  <button @click="show = !show">
    Toggle render
  </button>

  <transition
    name="custom-classes-transition"
    enter-active-class="animate__animated animate__tada"
    leave-active-class="animate__animated animate__bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>
复制代码
const Demo = {
  data() {
    return {
      show: true
    }
  }
}

Vue.createApp(Demo).mount('#demo')
复制代码

💡 如果使用 animation 属性实现动效,而不是 transition 控制过渡,则不需要指定 v-enter-from/v-leave-fromv-enter-to/v-leave-to 的状态,只需要在 v-enter-active/v-leave-active 过渡中设定 CSS 属性 animation 即可,其中在关键帧中指定了 0%100% 状态时的样式就是开始和结束的状态

<div id="demo">
  <button @click="show = !show">
    Toggle show
  </button>
  <transition name="bounce">
    <p v-if="show">hello</p>
  </transition>
</div>
复制代码
const Demo = {
  data() {
    return {
      show: true
    }
  }
}

Vue.createApp(Demo).mount('#demo')
复制代码
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}
复制代码

💡 Vue 可以自动得出过渡效果的完成时机,以「拔除」添加到元素上的相关的 class 类名,但是如果(不推荐)同时使用了 CSS 的属性 trasitionanimation 设置动画,可能会因为两种过渡时间不同,比如 animation 很快的被触发并完成了,而 transition 效果还没结束,而出现一些意想不到的动画 Bug,这时候可以为容器添加选项 type 并设置值为 animationtransition显式地声明你需要 Vue 监听的过渡动画类型;也可以在容器元素上通过 prop 显式地指定持续时间(以毫秒计) <transition :duration="1000">

💡 动效如果在初次加载页面时不生效,可以为元素 <transition> 添加属性 appear 设置节点初始渲染的过渡。

钩子函数

Vue 会在过渡的不同阶段同时触发相应事件,可以在内置组件 <transition> 监听这些事件,然后通过 JS 在事件处理函数中「手动」控制动画,一般是设置元素的 attribute 样式,或「对接」其他动画框架,例如 gsap

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
  :css="false"
>
  <!-- ... -->
</transition>
复制代码
// ...
methods: {
  /*
   * 元素进入
   */
  beforeEnter(el) {
    // ...
  },
  // 当与 CSS 结合使用时
  // 回调函数 done 是可选的
  enter(el, done) {
    // ...
    done()
  },
  afterEnter(el) {
    // ...
  },
  enterCancelled(el) {
    // ...
  },

  /*
   * 元素离开
   */
  beforeLeave(el) {
    // ...
  },
  // 当与 CSS 结合使用时
  // 回调函数 done 是可选的
  leave(el, done) {
    // ...
    done()
  },
  afterLeave(el) {
    // ...
  },
  // leaveCancelled 只用于 v-show 中
  leaveCancelled(el) {
    // ...
  }
}
复制代码
  • @before-enter="beforeEnter(el)" 可以传递执行动画的元素
  • @enter="enter(el, done)" 需要在回调函数中,在动画执行执行完成时调用 done() 告知 Vue 动画结束,必须执行一次 done(),否则它们将被同步调用,过渡会立即完成。
  • @after-enter="afterEnter"
  • @enter-cancelled="enterCancelled" 元素离开页面触发的事件
  • @before-leave="beforeLeave"
  • @leave="leave" 需要在回调函数中,在动画执行执行完成时调用 done() 告知 Vue 动画结束,必须执行一次 done(),否则它们将被同步调用,过渡会立即完成。
  • @after-leave="afterLeave"
  • @leave-cancelled="leaveCancelled"

💡 虽然这些钩子函数可以结合 CSS transitions/animations 使用,也可以单独使用。如果仅使用 JS 过渡,推荐为 <transition> 添加 v-bind:css="false" 属性绑定,Vue 会跳过 CSS 的检测,这也可以避免过渡过程中受 CSS 的影响。

多个元素间过渡

在多元素过渡时,默认模式是两个元素的进入&离开是同时进行的,可能会导致页面布局乱掉,可以将过渡模式改成 <transition mode="out-in"></transition> 让当前元素先进行过渡离开,完成之后新元素过渡进入

<transition name="fade" mode="out-in">
  <!-- ... the buttons ... -->
</transition>
复制代码

💡 对于使用 v-if/v-else 实现的多元素之间进行切换时,🎉 Vue 3 已经默认为 v-if/v-else/v-else-if 的各分支项自动生成唯一的 key,因此这些元素的 key 属性不再必要了,如果自己添加 key 属性时需要设置唯一可区分的值,否则相同标签名的元素之间切换时只会替换内部的内容,而不会进行触发整个元素切换过渡。

列表重排过渡

使用组件 <transition-group></transition-group> 作为容器,其内可以有多个节点(而 <transition> 只允许一个根节点,虽然可以包裹多个元素,但是每次 v-ifv-show 切换后,都只能渲染一个根节点),要记得其内部的各个节点总是需要提供属性 key 作为唯一标识。

<transition-group name="list" tag="p">
  <span v-for="item in items" :key="item" class="list-item">
    {{ item }}
  </span>
</transition-group>
复制代码

Vue 也是在过渡的不同阶段,为元素插入以上所述的 6 个不同后缀 class,可以使用这些 class 作为选择器设置样式。

  • 🎉 v-enter-from
  • v-enter-active
  • v-enter-to
  • 🎉 v-leave-from
  • v-leave-active
  • v-leave-to

此外还特别针对性地新增了后缀为 v-moveclass 类属性(如果设置了 name 属性 <transition-group name="animationType"></transition-group>,则列表重排时插入到列表项的 class 类属性的前缀就会改变,即为 animationType-move;还可以像之前的类名一样,即通过 move-class attribute 手动设置)。一般会通过 v-move 这个选择器设置 CSS 属性 transform ,指定过渡 timing 和 easing 曲线,然后 Vue 会将元素从之前的位置平滑过渡新的位置,实现一个称为 FLIP 的动画效果。

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

<div id="flip-list-demo">
  <button @click="shuffle">Shuffle</button>
  <transition-group name="flip-list" tag="ul">
    <li v-for="item in items" :key="item">
      {{ item }}
    </li>
  </transition-group>
</div>
复制代码
const Demo = {
  data() {
    return {
      items: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
  },
  methods: {
    shuffle() {
      // 使用 lodash 的 shuffle 对列表元素进行重排
      this.items = _.shuffle(this.items)
    }
  }
}

Vue.createApp(Demo).mount('#flip-list-demo')
复制代码
.flip-list-move {
  transition: transform 0.8s ease;
}
复制代码

💡 🎉 在 Vue 3 中,<transition-group> 默认不再渲染根节点,但可以通过属性 tag 来设置,例如 tag="div" 将使用 <div> 元素作为容器包装列表。

💡 FLIP 动效对于 display: inline 元素无效,对于行内元素,如 <span>,可以设置为 display: inline-block

动态过渡

Vue 是数据驱动画面的,因此过渡动效的类型也是可以基于数据进行变化的,如通过 <transition :name="transitionName"> 将属性 name 绑定到变量,就可以实时操作动效的类型,因为事件钩子是方法,它们可以访问任何上下文中的数据,这意味着根据组件的状态不同,过渡可以有不同的表现,例如实现轮播图点击左右箭头时可以实现切换方向和过渡的不同。

状态过渡

对于元素的内容/数据本身的改变也可以设置过渡动效,包括数字和运算、颜色的显示、SVG 节点的位置、元素的大小和其他的 property。

一般以数值表示的样式属性都可以设置动效,可以结合 Vue 的响应式和组件系统,使用第三方库来实现切换元素的过渡状态,如 tween.jsgsap

文章分类
前端
文章标签