Vue学习篇-过渡&动画

123 阅读5分钟

动画基本使用

本文是依据vue中文文档,再结合自己的理解,使用vue3setup语法糖敲一遍代码,仅供学习记录和参考

类名+过渡实现

transition包裹单元素(或者多个元素只渲染一个,或动态组件),添加name,再定义样式类名,vue会帮助你在特定时间给被包裹元素添加类名,类名如下:

  1. v-enter-from:进入过渡开始时的状态,在元素被插入之后立马生效,打开浏览器调试查看元素时基本上看不到此类名;
  2. v-enter-active:进入过渡生效时的状态,过渡的整个阶段都存在,过渡完成后立马移除,这个类常用于定义添加css属性transition来实现平缓过渡效果;
  3. v-enter-to:进入过渡结束的状态,元素被插入后的下一帧就生效,通过定义的css属性transitionv-enter-from定义的可平缓过渡的不同样式来实现平缓过渡效果;
  4. v-leave-from:离开过渡开始时的状态,在元素即将要被移除时生效;
  5. v-leave-active:离开过渡生效时的状态,与v-enter-active同理;
  6. v-leave-to:离开过渡的结束状态,与v-enter-to同理,到达这个状态之后,将立马移除元素。

一般情况下,v-enter-activev-leave-activecss代码一样,都是添加css样式transition实现平缓过渡;v-enter-fromv-leave-tocss代码一样,都是定义元素消失时的状态;v-enter-tov-leave-fromcss代码一样,都是定义元素存在时的状态

<button @click="show = !show">toggle</button>
<transition name="noice">
    <h3 v-if="show">hello world</h3>
</transition>
​
……
​
<style>
    .noice-enter-active,
    .noice-leave-active {
        transition: all 0.3s linear;
    }
    .noice-enter-from,
    .noice-leave-to {
        opacity: 0;
    }
    .noice-enter-to,
    .noice-leave-from {
        opacity: 1;
    }
</style>

官方配图很形象 官网过渡&动画图解

类名+动画实现

可通过自定义动画@keyframes name来定义动画来实现动画效果

<style>
    .noice-enter-active {
        animation: bounce-in 1s;
    }
    .noice-leave-active {
        animation: bounce-in 1s reverse;
    }
    @keyframes bounce-in {
        0% {
            transform: scale(0);
        }
        50% {
            transform: scale(1.25);
        }
        100% {
            transform: scale(1);
        }
    }
</style>

自定义类名

此处按照官网来使用常用的animate.css样式库

// vue3中
yarn add animate.css
​
// main.js中直接引入即可用
import 'animate.css';

开始使用

<template>
    <button @click="show = !show">toggle</button>
    <transition 
        enter-active-class="animate__animated animate__tada"
        leave-active-class="animate__animated animate__bounceOutRight"
    >
        <h3 v-if="show">hello world</h3>
    </transition>
</template>

同时使用过渡和动画

Vue本身有帮助监听过渡或者动画结束的功能,但是如果同时使用了过渡和动画,监听可能会出现异常,这时就需要告诉Vue要监听是动画结束还是过渡结束,通过给标签设置type来控制,可选值为animationtransition

<transition type="animation">
    <h3 v-if="show">hello world</h3>
</transition>

显性的过渡持续时间

Vue可自动得出过渡完成的时间,但可能在某些情况下如定义了一系列的过渡骚操作,可能会导致过渡完成效果不如预期,此时可通过定义duration来自定义时间,毫秒为单位

<transition :duration="1000">
    <h3 v-if="show">hello world</h3>
</transition>

js钩子

可以声明钩子函数,会在对应时间执行该函数,可以配合过渡或者动画使用。需注意enterleave钩子需手动执行done回调,否则会视为直接过渡完成。Vue默认会添加CSS类名,如果未使用类名时,可添加:class="false"来关闭添加对应类名,略微提高性能且避免类名所带来影响

  • before-enter:开始进入之前触发
  • enter:开始进入时触发
  • after-enter:开始进入之后触发
  • enter-cancelled:进入完成之后触发
<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>

js钩子结合gsap库实现过渡

gsap是一个js动画框架,此处配合js钩子实现过渡动画,了解更多gsap后可实现更炫酷的动画

此处立下flag,后面要学习一下这个

先安装

yarn add gsap -S

再使用

<transition
  @before-enter="before"
  @enter="enter"
  @leave="leave"
  :css="false"
>
</transition>
​
<script setup>
    const before = el => {
      gsap.set(el, {
        opacity: 0,
      });
    };
    const enter = (el, done) => {
      gsap.to(el, {
        opacity: 1,
        onComplete: done,
      });
    };
    const leave = (el, done) => {
      gsap.to(el, {
        opacity: 0,
        onComplete: done,
      });
    };
</script>

其他属性

  1. 初始渲染的过渡 appear

    默认情况下,在初次渲染时,元素不会执行定义好的过渡效果,添加appear属性来指定初次渲染也执行过渡效果,<transition appear>

  2. 过渡模式 mode

    当定义的过渡是两个组件相互过渡时会出现一个问题,每次过渡时都是新的元素先过渡出现,完了之后已有元素才离开,效果看起来特别不合适,Vue提供了一个过渡模式,有两个可选项

    • in-out:新元素先进行进入过渡,完成之后当前已有元素才过渡离开
    • out-in:当前元素先过渡离开,完成之后新元素再过渡进入

    指定<transition mode="out-in">即可实现旧元素先离开新元素再进场的效果

列表过渡

列表的进入/离开

<transition>改为<transition-group>,还是添加之前的类名

<template>
  <div id="list-demo">
    <button @click="add">添加</button>
    <button @click="remove">移除</button>
    <transition-group name="list" tag="ul">
      <li v-for="item in items" :key="item" class="list-item">
        {{ item }}
      </li>
    </transition-group>
  </div>
</template><script setup>
  import { ref } from 'vue';
  const items = ref([1, 2, 3, 4, 5, 6]);
  let nextNum = items.value.length + 1;
  function random () {
    return Math.floor(Math.random() * items.value.length);
  };
  function add () {
    items.value.splice(random(), 0, nextNum++);
    // items.value.push(random())
  };
  function remove () {
    items.value.splice(random(), 1);
  };
</script><style lang="scss" scoped>
  .list-enter-active,
  .list-leave-active {
    transition: all 0.3s;
  }
  .list-enter-from,
  .list-leave-to {
    opacity: 0;
    transform: translateX(30px);
  }
</style>

列表的移动过渡

<transition-group>添加name-move类可以为定位的元素添加移动过渡,两个需要注意的点,一是给每一个元素添加定位,二是给对应的name-move添加过渡样式,此处引用lodash库的shuffle方法实现重新排序

<button @click="shuffle">重排</button><script setup>
    import _ from 'lodash';
    const shuffle = () => {
        items.value = _.shuffle(items.value);
    };
</script><style>
    .list-item {
        position: relative;
    }
</style>

列表的交错过渡

配合gsap的动画延迟delay,可实现列表的依次出现或消失

<template>
  <div id="list-demo">
    <input type="text" v-model="key">
    <transition-group 
      name="list" 
      tag="ul"
      :css="false"
      @before-enter="beforeEnter"
      @enter="enter"
      @leave="leave"
    >
      <li v-for="(item, i) of showList" :key="item" :data-i="i">{{ item }}</li>
    </transition-group>
  </div>
</template>
​
<script setup>
  import { ref, computed } from 'vue';
  import { gsap } from 'gsap';
  const items = ref(['Li Ming', 'Jery', 'Honey', 'Jim', 'Tim', 'Tony']);
  const key = ref('');
  const showList = computed(() => {
    return items.value.filter(item => {
      return item.toLowerCase().indexOf(key.value.toLocaleLowerCase()) !== -1;
    })
  });
  const beforeEnter = el => {
    el.style.opacity = 0
    el.style.height = 0
  };
  const enter = (el, done) => {
    gsap.to(el, {
      opacity: 1,
      height: '1.4em',
      delay: el.dataset.i * 0.1,
      onComplete: done,
    });
  };
  const leave = (el, done) => {
    gsap.to(el, {
      opacity: 0,
      height: 0,
      delay: el.dataset.i * 0.1,
      onComplete: done,
    });
  };
</script>

可复用过渡

封装一个组件,用过渡标签包裹根元素,过渡标签内加上插槽,在组件内定义统一的过渡效果,父组件使用时传入插槽内容即可

  • 封装子组件 TheTransition.vue
<template>
  <transition name="noice" mode="out-in" appear>
    <slot></slot>
  </transition>
</template>

<script setup>

</script>

<style lang="scss" scoped>
  .noice-leave-active,
  .noice-enter-active {
    transition: all 0.3s;
  }
  .noice-leave-to,
  .noice-enter-from {
    opacity: 0;
  }
</style>
  • 父组件内使用
<button @click="show = !show">toggle</button>
<the-transition>
    <h3 v-show="show">阿西</h3>
</the-transition>

动态过渡

过渡标签上面的属性都是可以进行动态绑定的,而且js钩子中亦可使用js变量,这就代表着过渡有着极高的灵活性,官网目前没有完整的示例,在此不做实例,有需要时再做研究

唯一的限制是你的想象力。

状态过渡

数字状态过渡

结合gsap库,可实现数字唰唰唰改变的效果

<template>
  <div>
    <input type="number" v-model="value">
    <h3>{{ showVal.toFixed(0) }}</h3>
  </div>
</template><script setup>
  import { ref, watch } from 'vue';
  import { gsap } from 'gsap';
  const value = ref(0);
  const showVal = ref(0);
  watch(value, val => {
    console.log(val)
    gsap.to(showVal, {
      duration: 0.3,
      value: val,
    })
  });
</script>

将过渡封装成组件

思路是将每个可能改变的数字封装进组件,组件内部监听数字改变,执行gsap的方法

  • 子组件 GsapShua.vue
<template>
  <span>
    {{ showVal.toFixed(0) }}
  </span>
</template><script setup>
  import { ref, watch } from 'vue';
  import { gsap } from 'gsap';
  const props = defineProps({
    value: '',
  });
  const showVal = ref(props.value);
  
  watch(() => props.value, val => {
    gsap.to(showVal, {
      duration: 0.3,
      value: val,
    })
  });
​
</script>
  • 父组件使用
<template>
  <div>
    <input type="number" v-model="num1" /> +
    <input type="number" v-model="num2" /> = {{ total }}
    <br />
    <GsapShua :value="num1" /> + <GsapShua :value="num2" /> = <GsapShua :value="total" />
  </div>
</template><script setup>
  import { computed, ref, watch } from 'vue';
  import GsapShua from '@/components/GsapShua.vue';
  const num1 = ref(0);
  const num2 = ref(0);
  const total = computed(() => {
    return num1.value + num2.value;
  })
</script>

另外两个看着比较高级复杂的demo就不做实现了

参考