Vue动画实现以及动画库的使用 animate.css,gsap库

Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:

  • 在 CSS 过渡和动画中自动应用 class
  • 可以配合使用第三方 CSS 动画库,如 Animate.css
  • 在过渡钩子函数中使用 JavaScript 直接操作 DOM
  • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

vue 动画 (transition)

  1. 使用命名形式
<transition name="t" mode="out-in" appear>
  <h1 v-if="isShow">Hello World</h1>
</transition>
<style>
  .t-enter-active {
    animation: bounceInUp 1s ease-in-out;
  }
  .t-leave-active {
    animation: bounceOutDown 1s ease-in-out;
  }
</style>
  1. 使用加类名形式
<transition
  enter-active-class="animate__animated  animate__backInDown"
  leave-active-class="animate__animated animate__backOutDown dir"
>
  <h1 v-if="isShow">Hello LIUJUNJIE</h1>
</transition>

<style>
  .dir {
    animation-duration: reverse;
  }
</style>

transition 组件的生命周期钩子

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
>
  <h1 v-if="isShow">Hello World</h1>
</transition>

不添加 css 动画

<transition :css="false">
  <h1 v-if="isShow">Hello World</h1>
</transition>

使用 animate.css

Animate.css | A cross-browser library of CSS animations.

npm install animate.css

可以使用添加类名的方式, 也可以使用动画形式如上列代码所示

使用 gsap

npm install gsap
<template>
  <div>
    <div>
      <button @click="isShow = !isShow">显示/隐藏</button>
    </div>
    <transition @enter="enter" @leave="leave">
      <h1 v-if="isShow">Hello World</h1>
    </transition>
  </div>
</template>

<script lang="ts" setup>
  import { ref } from "vue"
  import { gsap } from "gsap"

  const isShow = ref(true)

  const enter = (el: any, done: any) => {
    console.log("enter")
    gsap.from(el, {
      scale: 0,
      x: 200,
      y: 200,
      onComplete: done,
    })
  }

  const leave = (el: any, done: any) => {
    console.log("leave")
    gsap.to(el, {
      scale: 0,
      x: 200,
      y: 200,
      onComplete: done,
    })
  }
</script>
  • el 为动画对象
  • done 为动画结束调用
  • 其他属性可以详细查看 gsap

Inabpq.gif

列表过度动画

  • 列表过度动画使用transition-group
  • 其他属性与transition相同
<template>
  <div>
    <input type="text" v-model="text" />
    <button @click="addItem">添加列表</button>
    <button @click="removeItem">删除列表</button>
    <ol>
      <transition-group name="item">
        <li class="item" v-for="(item, index) in list" :key="item">
          {{ item }}
        </li>
      </transition-group>
    </ol>
  </div>
</template>
<script setup lang="ts">
  import { reactive, ref } from "@vue/reactivity"
  let list = reactive(["hello world", "吃饭", "睡觉", "打豆豆"])

  const text = ref("")

  const addItem = () => {
    let index = Math.round(Math.random() * (list.length - 1))
    list.splice(index, 0, text.value + list.length)
  }

  const removeItem = () => {
    let index = Math.round(Math.random() * (list.length - 1))
    list.splice(index, 1)
  }
</script>
<style>
  body {
    padding: 50px;
  }
  * {
    margin: 0;
    padding: 0;
  }
  input {
    font-size: 18px;
    padding: 10px;
  }
  .item {
    width: 100%;
    background-color: #eeee;
    padding: 10px;
    border-radius: 10px;
    margin-top: 30px;
  }
  .item-enter-active {
    animation: lightSpeedInLeft 0.5s ease-in-out;
  }
  .item-leave-active {
    animation: lightSpeedInLeft 0.3s ease-in-out reverse;
    position: absolute;
    z-index: 1111;
  }
  .item-move {
    transition: 0.5s ease;
  }
</style>

动画列表1.gif

列表动画交替过度(解决一起添加或移除的生硬效果)

<template>
  <div>
    <input type="text" v-model="text" />
    <transition-group
      name="item"
      tag="ol"
      :css="false"
      @enter="enter"
      @leave="leave"
      @beforeEnter="beforeEnter"
    >
      <li
        class="item"
        v-for="(item, index) in showNames"
        :key="item"
        :data-index="index"
      >
        {{ item }}
      </li>
    </transition-group>
  </div>
</template>
<script setup lang="ts">
  import { reactive, ref, computed } from "@vue/reactivity"
  import gsap from "gsap"
  let list = reactive([
    "hello world",
    "吃饭",
    "睡觉",
    "打豆豆",
    "aaaaa",
    "刘俊杰",
    "刘俊杰",
    "刘小杰",
    "刘俊杰",
    "刘俊杰",
  ])

  const text = ref("")

  const showNames = computed(() => {
    return list.filter((item) => item.indexOf(text.value) !== -1)
  })

  const beforeEnter = (el) => {
    el.style.opacity = 0
    el.style.height = 0
  }

  const enter = (el, done) => {
    gsap.to(el, {
      opacity: 1,
      height: "1.5em",
      delay: el.dataset.index * 0.1,
      onComplete: done,
    })
  }
  const leave = (el, done) => {
    gsap.to(el, {
      opacity: 0,
      height: 0,
      delay: el.dataset.index * 0.1,
      onComplete: done,
    })
  }
</script>
<style>
  body {
    padding: 50px;
  }
  * {
    margin: 0;
    padding: 0;
  }
  input {
    font-size: 18px;
    padding: 10px;
  }
  .item {
    width: 100%;
    background-color: #eeee;
    padding: 10px;
    border-radius: 10px;
    margin-top: 30px;
  }
  .item-enter-active {
    animation: lightSpeedInLeft 0.5s ease-in-out;
  }
  .item-leave-active {
    animation: lightSpeedInLeft 0.3s ease-in-out reverse;
    position: absolute;
    z-index: 1111;
  }
  .item-move {
    transition: 0.5s ease;
  }
</style>

动画列表3.gif