VueJS 过渡动画

  • 定义概述

    Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。
    包括以下工具:
    • 在 CSS 过渡和动画中自动应用 class
    • 可以配合使用第三方 CSS 动画库,如 Animate.css
    • 在过渡钩子函数中使用 JavaScript 直接操作 DOM
    • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
    在这里,我们只会讲到进入、离开过渡,你也可以看下一章的列表过渡,动态过渡。
  • 单元素/组件的过渡

    Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
    • 条件渲染 (使用 v-if)
    • 条件展示 (使用 v-show)
    • 动态组件
    • 组件根节点
    这里是一个典型的例子:
    <div id="demo">
          <button v-on:click="show = !show">
              Toggle
          </button>
          <transition name="fade">
              <p v-if="show">hello</p>
          </transition>
      </div>
      <script>
      new Vue({
        el: '#demo',
        data: {
          show: true
        }
      });
      </script>
      <style>
      .fade-enter-active, .fade-leave-active {
        transition: opacity .5s;
      }
      .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
        opacity: 0;
      }
      </style>
    
    尝试一下
    当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:
    1. 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
    2. 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
    3. 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同)

    过渡的类名

    在进入/离开的过渡中,会有 6 个 class 切换。
    1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
    2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
    3. v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
    4. v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
    5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
    6. v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
    transition
    对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter 会替换为 my-transition-enter
    v-enter-activev-leave-active 可以控制进入/离开过渡的不同的缓和曲线,在下面会有个示例说明。

    CSS 过渡

    常用的过渡都是使用 CSS 过渡。
    下面是一个简单例子:
    <div id="example-1">
          <button @click="show = !show">
              Toggle render
          </button>
          <transition name="slide-fade">
              <p v-if="show">hello</p>
          </transition>
      </div>
      <script>
      new Vue({
          el: '#example-1',
          data: {
            show: true
          }
      })
      </script>
      <style>
      // 可以设置不同的进入和离开动画,设置持续时间和动画函数 
      .slide-fade-enter-active {
        transition: all .3s ease;
      }
      .slide-fade-leave-active {
        transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
      }
      .slide-fade-enter, .slide-fade-leave-to
      /* .slide-fade-leave-active for below version 2.1.8 */ {
        transform: translateX(10px);
        opacity: 0;
      }
      </style>
    
    尝试一下

    CSS 动画

    CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。
    示例:(省略了兼容性前缀)
    <div id="example-2">
          <button @click="show = !show">Toggle show</button>
          <transition name="bounce">
              <p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.</p>
          </transition>
      </div>
      <script>
      new Vue({
          el: '#example-2',
          data: {
            show: true
          }
      })
      </script>
      <style>
      .bounce-enter-active {
        animation: bounce-in .5s;
      }
      .bounce-leave-active {
        animation: bounce-in .5s reverse;
      }
      @keyframes bounce-in {
          0% {
              transform: scale(0);
          }
          50% {
              transform: scale(1.5);
          }
          100% {
              transform: scale(1);
          }
      }
      </style>
    
    尝试一下

    自定义过渡的类名

    我们可以通过以下特性来自定义过渡类名:
    • enter-class
    • enter-active-class
    • enter-to-class (2.1.8+)
    • leave-class
    • leave-active-class
    • leave-to-class (2.1.8+)
    他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。
    示例:
    <link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
      
      <div id="example-3">
          <button @click="show = !show">
          Toggle render
          </button>
          <transition
          name="custom-classes-transition"
          enter-active-class="animated tada"
          leave-active-class="animated bounceOutRight"
          >
          <p v-if="show">hello</p>
          </transition>
      </div>
      <script>
      new Vue({
          el: '#example-3',
          data: {
            show: true
          }
      })
      </script>
    
    尝试一下

    JavaScript 过渡钩子函数

    可以在属性中声明 JavaScript 钩子
    <transition
          v-on:before-enter="beforeEnter"
          v-on:enter="enter"
          v-on:after-enter="afterEnter"
          v-on:enter-cancelled="enterCancelled"
          
          v-on:before-leave="beforeLeave"
          v-on:leave="leave"
          v-on:after-leave="afterLeave"
          v-on:leave-cancelled="leaveCancelled"
      >
      // html
      </transition>
      <script>
      // ...
      methods: {
        // --------
        // 进入中
        // --------
      
        beforeEnter: function (el) {
          // ...
        },
        // 当与 CSS 结合使用时
        // 回调函数 done 是可选的
        enter: function (el, done) {
          // ...
          done()
        },
        afterEnter: function (el) {
          // ...
        },
        enterCancelled: function (el) {
          // ...
        },
      
        // --------
        // 离开时
        // --------
      
        beforeLeave: function (el) {
          // ...
        },
        // 当与 CSS 结合使用时
        // 回调函数 done 是可选的
        leave: function (el, done) {
          // ...
          done()
        },
        afterLeave: function (el) {
          // ...
        },
        // leaveCancelled 只用于 v-show 中
        leaveCancelled: function (el) {
          // ...
        }
      }
      </script>
    
    这些钩子函数可以结合 CSS transitions/animations 使用,也可以单独使用。
    当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。
    推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
    一个使用 Velocity.js 的简单例子:
    //Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
      <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
      <div id="example-4">
        <button @click="show = !show">
          Toggle
        </button>
        <transition
          v-on:before-enter="beforeEnter"
          v-on:enter="enter"
          v-on:leave="leave"
          v-bind:css="false"
        >
          <p v-if="show">
            Demo
          </p>
        </transition>
      </div>
      <script>
        new Vue({
          el: '#example-4',
          data: {
            show: false
          },
          methods: {
            beforeEnter: function (el) {
              el.style.opacity = 0
              el.style.transformOrigin = 'left'
            },
            enter: function (el, done) {
              Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
              Velocity(el, { fontSize: '1em' }, { complete: done })
            },
            leave: function (el, done) {
              Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
              Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
              Velocity(el, {
                rotateZ: '45deg',
                translateY: '30px',
                translateX: '30px',
                opacity: 0
              }, { complete: done })
            }
          }
        })
      </script>
    
    尝试一下
  • 初始渲染的过渡

    可以通过 appear 特性设置节点在初始渲染的过渡
    <transition appear>
      //....
      </transition>
    
    这里默认和进入/离开过渡一样,同样也可以自定义 CSS 类名。
    <transition
          appear
          appear-class="custom-appear-class"
          appear-to-class="custom-appear-to-class" (2.1.8+)
          appear-active-class="custom-appear-active-class"
      >
      //....
      </transition>
    
    自定义 JavaScript 钩子:
    <transition
          appear
          v-on:before-appear="customBeforeAppearHook"
          v-on:appear="customAppearHook"
          v-on:after-appear="customAfterAppearHook"
          v-on:appear-cancelled="customAppearCancelledHook"
      >
      //....
      </transition>
    
    在上面的例子中,无论是 appear 特性还是 v-on:appear 钩子都会生成初始渲染过渡。
  • 多个元素的过渡

    我们之后讨论多个组件的过渡,对于原生标签可以使用 v-if/v-else 。最常见的多标签过渡是一个列表和描述这个列表为空消息的元素:
    <transition>
        <table v-if="items.length > 0">
          //...
        </table>
        <p v-else>Sorry, no items found.</p>
      </transition>
    
    可以这样使用,但是有一点需要注意:
    当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在 <transition> 组件中的多个元素设置 key 是一个更好的实践。
    示例:
    <transition>
          <button v-bind:key="docState">
              {{ buttonMessage }}
          </button>
      </transition>
      <script>
      // ...
      computed: {
        buttonMessage: function () {
          switch (this.docState) {
            case 'saved': return 'Edit'
            case 'edited': return 'Save'
            case 'editing': return 'Cancel'
          }
        }
      }
      </script>
    
  • 多个组件的过渡

    多个组件的过渡简单很多 - 我们不需要使用 key 特性。相反,我们只需要使用动态组件:
    <transition name="component-fade" mode="out-in">
           <component v-bind:is="view"></component>
      </transition>
      <script>
      new Vue({
          el: '#transition-components-demo',
          data: {
            view: 'v-a'
          },
          components: {
            'v-a': {
              template: '<div>Component A</div>'
            },
            'v-b': {
              template: '<div>Component B</div>'
            }
          }
      })
      </script>
      <style>
      .component-fade-enter-active, .component-fade-leave-active {
        transition: opacity .3s ease;
      }
      .component-fade-enter, .component-fade-leave-to
      /* .component-fade-leave-active for below version 2.1.8 */ {
        opacity: 0;
      }
      </style>
    
    尝试一下
    有关列表过渡,动态过渡,可复用的过渡知识,在下一章节会详细讲解