[TOC]

过渡与动画基础

transform 形变、transition 过渡动画、animation 帧动画

也可以直接定义帧动画,更精确的控制,可以花里胡哨一点。

Vue 的 transition 过渡

在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验:
React框架本身并没有提供任何动画相关的API,所以在React中使用过渡动画我们需要使用一个第三方库

  • react-transition-group;

Vue中为我们提供一些内置组件和对应的API来完成动画,利用它们我们可以方便的实现过渡动画效果;

  • vue 中给单元素或者组件实现过渡动画,可以使用 transition 内置组件来完成动画。

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡:

  • 条件渲染 (使用 v-if)条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点 ```html

如果要添加展示到隐藏的过渡效果:**整个过程就像往返跑**。
```css
.dd-enter-from,
.dd-leave-to { // 一般用并集选择器写成一对,因为通常离开后要到达的目标状态和最开始的状态是一样的
  opacity: 0;
}

.dd-enter-to,
.dd-leave-from {
  opacity: 1;
}

.dd-enter-active,
.dd-leave-active {
  transition: opacity 1s ease;
}

仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。

Transition 组件的原理

当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:

  1. 自动嗅探目标元素是否应用了CSS过渡或者动画,如果有,那么在恰当的时机添加/删除 CSS类名
  2. 如果 transition 组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用;
  3. 如果没有找到JavaScript钩子并且也没有检测到CSS过渡/动画,DOM插入、删除操作将会立即执行;

一句话:原理就是自动嗅探 style 标签,自动添加类名到标签上。

添加的 css 类名

9. 过渡与动画 - 图1

  1. v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
  2. v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
  3. v-enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
  4. v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
  5. v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
  6. v-leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。

    过渡效果名

    我们可以给 组件传一个 name prop 来声明一个过渡效果名:对于一个有名字的过渡效果,对它起作用的过渡 class 会以其名字而不是 v 作为前缀。比如,上方例子中被应用的 class 将会是 fade-enter-active 而不是 v-enter-active。

    CSS 的 transition

    前后两个状态也是可以有持续时间,而不是一个时刻的快照,所以前后状态可以添加 css transition 过渡效果,实现更细腻的过渡。 ```css / 进入和离开动画可以使用不同 持续时间和速度曲线。 / .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; }

<a name="iSD5E"></a>
## animation 帧动画
前面我们是通过transition来实现的动画效果,另外我们也可以通过animation来实现。
```css
.dd-enter-active {
  animation: bounce 1s ease;
}

.dd-leave-active {
  animation: bounce 1s ease reverse; // 离开的动画可以直接取反
}

@keyframes bounce {
  0% {
    transform: scale(0)
  }

  50% {
    transform: scale(1.2);
  }

  100% {
    transform: scale(1);
  }
}

Transition 组件的属性

type 动画类型

Vue为了知道过渡的完成,内部是在监听 transitionend 或 animationend,到底使用哪一个取决于元素应用的
CSS规则:如果我们只是使用了其中的一个,那么Vue能自动识别类型并设置监听;

如果同时设置过渡和动画,而且动画时间不一致,那是以谁的动画为主呢?
在这种情况下,我们可以设置 type 属性为 animation 或者 transition 来明确的告知Vue监听的类型,也就是以谁的动画为主。

<transition name="dd" type="animation"> ... </transition>

duration 持续时间

对于同时设置了两种动画类型的问题,我们也可以显示的来指定过渡的时间,通过 duration 属性。

duration可以设置两种类型的值:

  • number类型:同时设置进入和离开的过渡时间;
  • object类型:分别设置进入和离开的过渡时间;

设置 duration 后过渡和动画定义的时间就无效了。

<transition name="dd" type="animation" :duration="1000"> ... </transition>

<transition 
  name="dd" 
  type="animation" 
  :duration="{ enter: 800, leave: 1000 }"> 
  ... 
</transition>

mode 过渡的模式

两个元素都有动画,那两个元素切换时动画就会有一个时间上的重合问题。
比如点击按钮元素 A 消失,元素 B 出现。那么会出现 A 在执行消失动画的时候,B 的进入动画同时也在执行的情况。离开动画和进入动画是同时的。

  • 动画在两个元素之间切换的时候存在的问题:

image.png
hello world 的消失动画没执行完,把后面进入的元素位置挤走了。

所以在多个元素切换的时候,我们需要给动画设置过渡模式,也就是执行动画的先后。

mode 属性:

  • out-in:离开动画执行完,再执行新元素的进入动画
  • in-out:先执行新元素的进入动画,再执行旧元素的离开动画,也就是说页面会同时出现新旧两个元素。

这种切换问题不止出现在两个元素的切换中,还发生在组件切换中,比如路由切换,动态组件切换。

appear 初次渲染

默认情况下,首次渲染的时候是没有动画的,如果我们希望给他添加上去动画,那么就可以增加另外一个属性 appear

  • 默认是 false,可以改成 true。v-bind:appear=”true”,简写直接写个 appear ,和 checked 一样。

    Transition 组件的应用场景

    一般动态组件或者路由的插槽,缓存组件,过渡动画组件一起连用。
    <router-view v-slot="hhh">
    <!-- <router-view v-slot ='{ Component }'> -->
    <transition name="ddd">
      <keep-alive>
        <component :is="hhh.Component"></component>
      </keep-alive>
    </transition>
    </router-view>
    

    animate.css 动画库

    image.png
    animate.css 有两种使用方式:
  1. 帧动画使用
  2. 类名使用

    帧动画使用

    官网中的动画名就是 @keyframes 的变量名。
    image.png ```css .dd-enter-active { animation: fadeInLeft 1s ease; }

.dd-leave-active { animation: fadeInLeft 1s ease reverse; }

<a name="dWoUW"></a>
## 类名使用
除了使用帧动画,也可以使用类名。右边复制按钮就能复制类名。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22919157/1661002957149-f47b51d0-c70f-40b7-9e54-d4adb06904f2.png#clientId=ud13c7fbf-9594-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=85&id=u50ca549d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=133&originWidth=283&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10404&status=error&style=none&taskId=ue51649dc-edd9-4c99-aa6a-66bb9cbc3eb&title=&width=181.12)<br />注意:使用所有效果的类名之前都要加上基础动画的类
```css
<h1 class="animate__animated animate__fadeInLeft">An animated element</h1>

但是像上面一样使用这些类名,transition 组件并不会识别。过渡效果的类,它默认只识别 v-enter-from 这些。
那怎么让其他的动画效果 class 生效呢?

transition 自定义过渡类

transition 组件提供以下 attribute 来让我们自定义过渡类名:这些属性也对应了动画的时间点。

  • enter-from-class
  • enter-active-class
  • enter-to-class
  • leave-from-class
  • leave-active-class
  • leave-to-class

他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css. 结合使用十
分有用。

<transition name="dd" enter-active-class="animate__animated animate__fadeInLeft" appear>
<transition 
  name="dd" 
  enter-active-class="animate__animated animate__fadeInLeft" 
  leave-active-class="animate__animated animate__fadeInLeft" 
  // leave-active-class="animate__animated animate__fadeInRight"
  appear
  mode="out-in"
  >
  <h1 v-if="isShow">hello VUE</h1>
</transition>

<style scoped lang="less">
  // 可以给 animate.css 的类进行自定义,比如上面离开也是从左边淡入,显然不可能,所以可以将从左淡入添加反转
  // 当然也可以直接给离开动画属性添加向右淡出的类名
  .animate__fadeInLeft {
      animation-direction: reverse; 
  }
</style>

gsap 动画库

image.png

JavaScript 钩子

在使用动画之前,我们先来看一下transition组件给我们提供的JavaScript钩子,这些钩子可以帮助我们监听动画执行到什么阶段了。
image.pngimage.pngimage.png
当我们使用JavaScript来执行过渡动画时,需要进行 done 回调,否则它们将会被同步调用,过渡会立即完成。
添加:css="false",也会让 Vue 会跳过 CSS 的检测,除了性能略高之外,这可以避免过渡过程中 CSS 规则的影响。

gsap 库的使用

image.pngimage.png

gsap 实现数字变化

在一些项目中,我们会见到数字快速变化的动画效果,这个动画可以很容易通过gsap来实现:
image.pngimage.png

transition-group 列表过渡

transition 组件是给单个元素,或者组件添加动画效果。这个元素可能会包裹很多子元素,但是本质还是动画只给最外层的元素添加了 class 类。
transition-group 是给所有包裹的元素添加动画效果,给所有元素添加了动画效果的 class 类。这样就能做到一种全体动画的效果。
比如一个数组中,每个元素的删除,增加,排序都会有动画效果。

使用 有如下的特点:

  • 默认情况下,它不会渲染一个元素的包裹器,但是你可以指定一个元素并以 tag attribute 进行渲染;
    • 比如 tag=”div” 让一个 div 做外层包裹
  • 过渡模式 mode 不可用,因为我们不再相互切换特有的元素;
  • 内部元素总是需要提供唯一的 key attribute 值;
  • CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身;

注意一点: 动画效果的元素使用行内元素可能会没有效果,比如 span 位移 transform 就没有效果,所以一般要转成行内非替换元素。

列表过渡的基本使用

我们来做一个案例:

  • 案例是一列数字,可以继续添加或者删除数字;
  • 在添加和删除数字的过程中,对添加的或者移除的数字添加动画;

image.png

一个注意点:元素执行消失动画的时候,依然占据 dom 位置,这时后面的元素想要执行往上挤的动画就没效果,卡住了。前面的元素一完全消失,后面的元素就会突然的弹上去,本来的动画效果被卡没了。
所以可以让元素执行消失动画时脱标:display: absolute;

<template>
  <div>
    <button @click="addNum">添加数字</button>
    <button @click="removeNum">删除数字</button>
    <button @click="shuffleNum">数字洗牌</button>

    <transition-group tag="p" name="why">
      <span v-for="item in numbers" :key="item" class="item">
        {{item}}
      </span>
    </transition-group>
  </div>
</template>

<script>
  import _ from 'lodash';

  export default {
    data() {
      return {
        numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        numCounter: 10
      }
    },
    methods: {
      addNum() {
        // this.numbers.push(this.numCounter++)
        this.numbers.splice(this.randomIndex(), 0, this.numCounter++)
      },
      removeNum() {
        this.numbers.splice(this.randomIndex(), 1)
      },
      shuffleNum() {
        this.numbers = _.shuffle(this.numbers); // lodash 将数字打乱洗牌
      },
      randomIndex() {
        return Math.floor(Math.random() * this.numbers.length) // 随机数
      }
    },
  }
</script>

<style scoped>
  .item {
    margin-right: 10px;
    display: inline-block; // 克服 span 无位移效果
  }

  .why-enter-from,
  .why-leave-to {
    opacity: 0;
    transform: translateY(30px);
  }

  .why-enter-active,
  .why-leave-active {
    transition: all 1s ease;
  }

  .why-leave-active {
    position: absolute; // 在执行离开动画时让元素脱标
  }

  .why-move {
    transition: transform 1s ease;
  }
</style>

列表的交错过渡案例

我们可以通过gsap的延迟delay属性,让元素一个接一个消失,做一个交替消失的动画:

<template>
  <div>
    <input v-model="keyword">
    <transition-group tag="ul" name="why" :css="false"
                      @before-enter="beforeEnter"
                      @enter="enter"
                      @leave="leave">
      <li v-for="(item, index) in showNames" :key="item" :data-index="index">
        {{item}}
      </li>
    </transition-group>
  </div>
</template>

<script>
  import gsap from 'gsap';

  export default {
    data() {
      return {
        names: ["abc", "cba", "nba", "why", "lilei", "hmm", "kobe", "james"],
        keyword: ""
      }
    },
    computed: {
      showNames() {
        return this.names.filter(item => item.indexOf(this.keyword) !== -1)
      }
    },
    methods: {
      beforeEnter(el) {
        el.style.opacity = 0;
        el.style.height = 0;
      },
      enter(el, done) {
        gsap.to(el, {
          opacity: 1,
          height: "1.5em",
          delay: el.dataset.index * 0.5,
          onComplete: done
        })
      },
      leave(el, done) {
        gsap.to(el, {
          opacity: 0,
          height: 0,
          delay: el.dataset.index * 0.5,
          onComplete: done
        })
      }
    }
  }
</script>

<style scoped>
  /* .why-enter-from,
  .why-leave-to {
    opacity: 0;
  }

  .why-enter-active,
  .why-leave-active {
    transition: opacity 1s ease;
  } */
</style>