• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • Transition 过度

    Vue 提供了两个内置组件,可以帮助你制作基于状态变化的过渡和动画:

    • <Transition>:在一个元素或组件,进入和离开 DOM 时,应用动画。
    • <TransitionGroup>:在一个元素或组件,在v-for列表中,被插入、移动或被移除时,应用动画。

    除了这两个组件,我们也可以通过其他技术手段来应用动画,比如切换 CSS 类或用状态绑定样式来驱动动画。这些其他的方法会在动画技巧章节中展开介绍到。

    <Transition>组件

    <Transition>是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以在通过默认插槽传递给它的元素或组件上,应用进入和离开动画。进入或离开可以由以下其中一种触发:

    1. v-if所触发的切换
    2. v-show所触发的切换
    3. 由特殊元素<component>切换的动态组件

    以下是最基本用法的示例:

    <button @click="show = !show">Toggle</button>
    <Transition>
      <p v-if="show">hello</p>
    </Transition>
    
    /* 下面我们会解释这些类是做什么的 */
    .v-enter-active,
    .v-leave-active {
      transition: opacity 0.5s ease;
    }
    
    .v-enter-from,
    .v-leave-to {
      opacity: 0;
    }
    
    <Transition>仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。

    当一个<Transition>组件中的元素被插入或移除时,会发生下面这些事情:

    • Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是,则一些 CSS 过渡类会在适当的时机被添加和移除。
    • 如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用。
    • 如果没有探测到 CSS 过渡或动画、没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧上执行。


    基于 CSS 的过渡

    过渡 CSS 类

    一共有 6 个应用于进入与离开过渡效果的 CSS 类。


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

    v-enter-activev-leave-active给了我们为进入和离开动画指定不同速度曲线的能力。


    为过渡命名

    可以通过一个name prop 来声明一种过渡:

    <Transition name="fade">
        ......
    </Transition>
    

    对于一个有名字的过渡,它的过渡相关 CSS 类会以其名字而不是v作为前缀。举个例子,上面被应用的 CSS 类将会是fade-enter-active而不是v-enter-active。这个fade过渡的 CSS 类将会是这样:

    .fade-enter-active,
    .fade-leave-active {
      transition: opacity 0.5s ease;
    }
    
    .fade-enter-from,
    .fade-leave-to {
      opacity: 0;
    }
    


    CSS 的 transition

    <Transition>一般都会搭配原生 CSS 过渡一起使用,正如你在上面的例子中所看到的那样。这个transition CSS 属性是一个简写形式,使我们可以一次定义一个过渡的各个方面,包括需要执行动画的属性、持续时间和速度曲线。

    下面是一个更高级的例子,使用不同的持续时间和速度曲线来过渡多个属性:

    <Transition name="slide-fade">
      <p v-if="show">hello</p>
    </Transition>
    
    /* 进入和离开动画可以使用不同 持续时间和速度曲线。 */
    .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 的 animation

    原生 CSS 动画和 CSS trasition 的应用方式基本上是相同的,只有一点不同,那就是*-enter-from不是在元素插入后立即移除,而是在一个animationend事件触发时被移除。

    对于大多数的 CSS 动画,我们可以简单地在*-enter-active*-leave-active类下面声明它们。下面是一个示例:

    <Transition name="bounce">
      <p v-if="show" style="text-align: center;">
        Hello here is some bouncy text!
      </p>
    </Transition>
    
    .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);
      }
    }
    


    自定义过渡类

    你也可以向<Transition>传递以下的props来指定自定义的过渡类:

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

    你传入的这些类会覆盖相应阶段的默认类名。这个功能在你想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用,比如Animate.css

    <!-- 假设你已经引入了 Animate.css -->
    <Transition
      name="custom-classes"
      enter-active-class="animate__animated animate__tada"
      leave-active-class="animate__animated animate__bounceOutRight"
    >
      <p v-if="show">hello</p>
    </Transition>
    


    同时使用 transition 和 animation

    Vue 需要附加事件侦听器,以便知道过渡何时结束。可以是transitionendanimationend,这取决于你所应用的 CSS 规则。如果你仅仅使用二者其中之一,Vue 可以自动探测到正确的类型。

    然而在某些场景中,你或许想要在同一个元素上同时使用它们两个,举个例子,触发了一个 CSS 动画的同时,由于副作用触发了另一个 CSS 过渡。此时你需要显式地传入type prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是animationtransition

    <Transition type="animation">...</Transition>
    


    深层级过渡与显式过渡时间

    尽管过渡类仅能应用在<Transition>的直接子元素上,我们还是可以使用深层级的 CSS 选择器,使深层级的元素发生过渡。

    <Transition name="nested">
      <div v-if="show" class="outer">
        <div class="inner">
          Hello
        </div>
      </div>
    </Transition>
    
    /* 应用于深层级元素的规则 */
    .nested-enter-active .inner,
    .nested-leave-active .inner {
      transition: all 0.3s ease-in-out;
    }
    
    .nested-enter-from .inner,
    .nested-leave-to .inner {
      transform: translateX(30px);
      opacity: 0;
    }
    

    我们甚至可以在深层级的元素上添加一个过渡延迟,这会创建一个交错进入动画序列:

    /* 延迟进入深层级元素以获得交错效果 */
    .nested-enter-active .inner {
      transition-delay: 0.25s;
    }
    

    然而,这会带来一个小问题。默认情况下,<Transition>组件会通过监听过渡根元素上的第一个transitionend或者animationend事件来尝试自动判断过渡何时结束。而在深层级的过渡中,期望的行为应该是等待所有内部元素的过渡完成。

    在这种情况下,你可以通过向<Transition>组件传入duration prop 来显式指定的过渡持续时间(以毫秒为单位)。总持续时间应该匹配延迟加上内部元素的过渡持续时间:

    <Transition :duration="550">...</Transition>
    

    如果有必要的话,你也可以用对象的形式传入,分开指定进入和离开所需的时间:

    <Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
    


    性能考量

    你可能注意到我们上面例子中展示的动画所用到的属性大多是transformopacity之类的。用这些属性制作动画非常高效,因为:

    • 他们在动画过程中不会影响到 DOM 结构,因此每一个动画帧都不会触发昂贵的 CSS 布局重新计算。
    • 大多数的现代浏览器都可以在执行transform动画时利用 GPU 进行硬件加速。

    相比之下,像height或者margin这样的属性会触发 CSS 布局变动,因此执行它们的动画效果更昂贵,需要谨慎使用。我们可以在CSS-Triggers这类的网站查询哪些属性会在执行动画时触发 CSS 布局变动。


    JavaScript 钩子

    你可以通过监听<Transition>组件事件的方式在过渡过程中挂上钩子函数:

    <Transition
      @before-enter="onBeforeEnter"
      @enter="onEnter"
      @after-enter="onAfterEnter"
      @enter-cancelled="onEnterCancelled"
      @before-leave="onBeforeLeave"
      @leave="onLeave"
      @after-leave="onAfterLeave"
      @leave-cancelled="onLeaveCancelled"
    >
      <!-- ... -->
    </Transition>
    
    // 在元素被插入到 DOM 之前被调用
    // 用这个来设置元素的 "enter-from" 状态
    function onBeforeEnter(el) {},
    
    // 在元素被插入到 DOM 之后的下一帧被调用
    // 用这个来开始进入动画
    function onEnter(el, done) {
      // 调用回调函数 done 表示过渡结束
      // 如果与 CSS 结合使用,则这个回调是可选参数
      done()
    }
    
    // 当进入过渡完成时调用。
    function onAfterEnter(el) {}
    function onEnterCancelled(el) {}
    
    // 在 leave 钩子之前调用
    // 大多数时候,你应该只会用到 leave 钩子
    function onBeforeLeave(el) {}
    
    // 在离开过渡开始时调用
    // 用这个来开始离开动画
    function onLeave(el, done) {
      // 调用回调函数 done 表示过渡结束
      // 如果与 CSS 结合使用,则这个回调是可选参数
      done()
    }
    
    // 在离开过渡完成、 且元素已从 DOM 中移除时调用
    function onAfterLeave(el) {}
    
    // 仅在 v-show 过渡中可用
    function leaveCancelled(el) {}
    

    这些钩子可以与 CSS 过渡/动画结合使用,也可以单独使用。

    在使用仅由 JavaScript 执行的动画时,最好是添加一个:css="false" prop。这显式地向 Vue 表明跳过 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡。

    <Transition
      ...
      :css="false"
    >
      ...
    </Transition>
    

    在有了:css="false"后,我们就全权自己负责控制什么时候过渡结束了。这种情况下对于@enter@leave钩子来说,回调函数done()就是必须的。否则,钩子将被同步调用,过渡将立即完成


    可重用过渡

    得益于 Vue 的组件系统,过渡是可以被重用的。要创建一个可被重用的过渡,我们需要为<Transition>组件创建一个包装组件,并向内传入插槽内容:

    <!-- MyTransition.vue -->
    <script>
    // JavaScript 钩子逻辑...
    </script>
    
    <template>
      <!-- 包装内置的 Transition 组件 -->
      <Transition
        name="my-transition"
        @enter="onEnter"
        @leave="onLeave"
      >
        <slot></slot> <!-- 向内传递插槽内容 -->
      </Transition>
    </template>
    
    <style>
    /*
      必要的 CSS...
      注意:避免在这里使用 <style scoped>
      因为那不会应用到插槽内容上
    */
    </style>
    

    现在 MyTransition 可以在导入后像内置组件那样使用了:

    <MyTransition>
      <div v-if="show">Hello</div>
    </MyTransition>
    


    出现时过渡

    如果你想在某个节点初次渲染时应用一个过渡效果,你可以添加appear attribute:

    <Transition appear>
      ...
    </Transition>
    


    元素间过渡

    除了通过v-if/v-show切换一个元素,我们也可以通过v-if/v-else/v-else-if在几个组件间进行切换过:

    <Transition>
      <button v-if="docState === 'saved'">Edit</button>
      <button v-else-if="docState === 'edited'">Save</button>
      <button v-else-if="docState === 'editing'">Cancel</button>
    </Transition>
    


    过渡模式

    在之前的例子中,进入和离开的元素都是在同时开始动画的,并且我们必须将它们设为position: absolute以避免二者同时存在时出现的布局问题。

    然而,在某些场景中这可能不是个好的方案,或者并不能符合行为预期。我们可能想要先执行离开动画,然后在其完成之后再执行元素的进入动画。手动编排这样的动画是非常复杂的,好在我们可以通过向<Transition>传入一个mode prop 来实现这个行为:

    <Transition mode="out-in">
      ...
    </Transition>
    

    <Transition>也支持mode="in-out",虽然这并不常用。


    组件间过渡

    <Transition>也可以用在动态组件之间:

    <Transition name="fade" mode="out-in">
      <component :is="activeComponent"></component>
    </Transition>
    


    动态过渡

    <Transition>props(比如name)也可以是动态的!这让我们可以根据状态变化动态地应用不同类型的过渡:

    <Transition :name="transitionName">
      <!-- ... -->
    </Transition>Fallthrough
    

    当你使用 Vue 的过渡类约定规则定义了 CSS 过渡/动画,并想在它们之间切换时,这可能很有用。

    你也可以根据你的组件的当前状态在 JavaScript 过渡钩子中应用不同的行为。在此篇的最后,我们可以得出结论,创建动态过渡的终极方式是创建可重用的过渡组件,这些组件接受prop来改变过渡的性质。现在在编写动画时,就真的只有你想不到,没有做不到的了。