乐趣区

关于vue.js:你到底懂不懂-Transition-组件

前言

欢送关注同名公众号《熊的猫》,文章会同步更新!

<Transition> 作为一个 Vue 中的内置组件,它能够将 进入动画 来到动画 利用到通过 默认插槽 传递给指标元素或组件上。

兴许你有在应用,然而始终不分明它的原理或具体实现,甚至不分明其外部提供的各个 class 到底怎么配合应用,想看源码又被其中各种引入搞得七荤八素 …

本篇文章就以 Transition 组件为外围,探讨其外围原理的实现,文中不会对其各个属性再做额定解释,毕竟这些看文档就够了,心愿可能给你带来帮忙!!!

Transition 内置组件

触发条件

<Transition> 组件的 进入动画 来到动画 可通过以下的条件之一触发:

  • 由 v-if 所触发的切换
  • 由 v-show 所触发的切换
  • 由非凡元素 <component name="x"> 切换的动静组件
  • 扭转非凡的 key 属性

再分类

其实咱们能够将以上状况进行 再分类

  • 组件 挂载 销毁

    • v-if 的变动
    • <component name="x"> 的变动
    • key 的变动
  • 组件 款式 属性 display: none | x 设置

    • v-show 的变动

扩大v-ifv-for 一起应用时,在 Vue2Vue3 中的不同

  • Vue2 中,当它们处于同一节点时,v-for 的优先级比 v-if 更高,即 v-if 将别离反复运行于每个 v-for 循环中,也就是 v-if 能够失常拜访 v-for 中的数据
  • Vue3 中,当它们处于同一节点时,v-if 的优先级比 v-for 更高,即此时只有 v-if 的值为 falsev-for 的列表就不会被渲染,也就是 v-if 不能拜访到 v-for 中的数据

六个过渡机会

总结起来就分为 进入 来到 动画的 初始状态、失效状态、完结状态,具体如下:

  • v-enter-from

    • 进入 动画的 起始状态
    • 在元素插入之前增加,在元素插入实现后的 下一帧移除
  • v-enter-active

    • 进入 动画的 失效状态,利用于整个进入动画阶段
    • 在元素被插入之前增加,在过渡或动画实现之后移除
    • 这个 class 能够被用来定义进入动画的持续时间、提早与速度曲线类型
  • v-enter-to

    • 进入 动画的 完结状态
    • 在元素插入实现后的下一帧被增加 (也就是 v-enter-from 被移除的同时),在过渡或动画实现之后移除
  • v-leave-from

    • 来到 动画的 起始状态
    • 在来到过渡成果被触发时立刻增加,在一帧后被移除
  • v-leave-active

    • 来到 动画的 失效状态,利用于整个来到动画阶段
    • 在来到过渡成果被触发时立刻增加,在 过渡或动画实现之后移除
    • 这个 class 能够被用来定义来到动画的持续时间、提早与速度曲线类型
  • v-leave-to

    • 来到 动画的 完结状态
    • 在一个来到动画被触发后的 下一帧 被增加 (即 v-leave-from 被移除的同时),在 过渡或动画实现之后移除

其中的 v 前缀是容许批改的,能够 <Transition> 组件传一个 name 的 prop 来申明一个过渡成果名,如下就是将 v 前缀批改为 **`
modal `** 前缀:

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

Transition 组件 & CSS transition 属性

以上这个简略的成果,外围就是两个机会:

  • v-enter-active 进入动画的 失效状态
  • v-leave-active 来到动画的 失效状态

再配合简略的 CSS 过渡属性就能够达到成果,代码如下:

<template>
  <div class="home">
    <transition name="golden">
      <!-- 金子列表 -->
      <div class="golden-box" v-show="show">
        <img
          class="golden"
          :key="idx"
          v-for="idx in 3"
          src="../assets/golden.jpg"
        />
      </div>
    </transition>
  </div>
  <!-- 钱袋子 -->
  <img class="purse" @click="show = !show" src="../assets/purse.png" alt="" />
</template>

<script setup lang="ts">
import {ref, computed} from 'vue'

const show = ref(true)
</script>

<style lang="less" scoped>
.home {min-height: 66px;}

.golden-box {
  transition: all 1s ease-in;

  .golden {
    width: 100px;
    position: fixed;
    transform: translate3d(0, 0, 0);
    transition: all .4s;

    &:nth-of-type(1) {
      left: 45%;
      top: 100px;
    }
    &:nth-of-type(2) {
      left: 54%;
      top: 50px;
    }
    &:nth-of-type(3) {
      right: 30%;
      top: 100px;
    }
  }

  &.golden-enter-active {
    .golden {transform: translate3d(0, 0, 0);
      transition-timing-function: cubic-bezier(0, 0.57, 0.44, 1.97);
    }
    .golden:nth-of-type(1) {transition-delay: 0.1s;}
    .golden:nth-of-type(2) {transition-delay: 0.2s;}
    .golden:nth-of-type(3) {transition-delay: 0.3s;}
  }

  &.golden-leave-active {.golden:nth-of-type(1) {transform: translate3d(150px, 140px, 0);
      transition-delay: 0.3s;
    }
    .golden:nth-of-type(2) {transform: translate3d(0, 140px, 0);
      transition-delay: 0.2s;
    }
    .golden:nth-of-type(3) {transform: translate3d(-100px, 140px, 0);
      transition-delay: 0.1s;
    }
  }
}

.purse {
  position: fixed;
  width: 200px;
  margin-top: 100px;
  cursor: pointer;
}
</style>

当然动画的成果是多种多样的,不仅只是局限于这一种,例如能够配合:

  • CSStransition 过渡属性(上述例子应用的计划)
  • CSSanimation 动画属性
  • gsap 库

    外围原理

通过上述内容其实不难发现其外围原理就是:

  • 组件(DOM)挂载 时,将过渡动效增加到该 DOM 元素上
  • 组件(DOM)卸载 时, 不是间接卸载 ,而是期待附加到 DOM 元素上的 动效执行实现 ,而后在真正执行卸载操作,即 提早卸载机会

在上述的过程中,<Transition> 组件会为 指标组件 / 元素 通过增加不同的 class 来定义 初始、失效、完结 三个状态,当进入下一个状态时会把上一个状态对应的 class 移除。

那么你可能会问了,v-show 的模式也不合乎 挂载 / 卸载 的模式呀,毕竟它只是在批改 DOM 元素的 display: none | x 的款式!

让源码中的正文来答复:

v-if<component name="x">key 管制组件 显示 / 暗藏 的形式是 挂载 / 卸载 组件,而 v-show 管制组件 显示 / 暗藏 的形式是 批改 / 重置 display: none | x 属性值,从实质上看形式不同,但从后果上看都属于管制组件的 显示 / 暗藏 ,即性能是统一的,而这里所说的 挂载 / 卸载 是针对大部分状况来说的,毕竟四种触发形式中就有三种合乎此状况。

实现 Transition 组件

所谓 Transition 组件毕竟是 Vue 的内置组件,换句话说,组件的编写要合乎 Vue 的标准(即 申明式写法 ),但为了更好的了解外围原理,咱们应该从 原生 DOM 的过渡开始(即 命令式写法)探讨。

原生 DOM 如何实现过渡?

所谓的 过渡动效 实质上就是一个 DOM 元素在 两种状态间的转换 浏览器 会依据咱们设置的过渡成果 自行实现 DOM 元素的过渡

状态的转换 指的就是 初始化状态 完结状态 的转换,并且配合 CSS 中的 transition 属性就能够实现两个状态间的过渡,即 静止过程

原生 DOM 元素挪动示例

假如要为一个元素在垂直方向上增加进场动效:从 原始地位 向上挪动 200px 的地位,而后在 1s 内静止回 原始地位

进场动效

用 CSS 形容

  // 形容物体
  .box {
    width: 100px;
    height: 100px;
    background-color: red;
    box-shadow: 0 0 8px;
    border-radius: 50%;
  }
  
  // 初始状态
  .enter-from {transform: translateY(-200px);
  }
  // 静止过程
  .enter-active {transition: transform 1s ease-in-out;}
  // 完结状态
  .enter-to {transform: translateY(0);
  }

用 JavaScript 形容

// 创立元素
const div = document.createElement('div')
div.classList.add('box')

// 增加 初始状态 和 静止过程
div.classList.add('enter-from')
div.classList.add('enter-active')

// 将元素增加到页面上
document.body.appendChild(div)

// 切换元素状态
div.classList.remove('enter-from')
div.classList.add('enter-to')

命令式编程 的步骤上来看,仿佛每一步都没有问题,但理论的过渡动画是不会失效的,尽管在代码中咱们有 状态的切换 ,但这个切换的操作对于 浏览器 来讲是在 同一帧 中进行的,所以只会渲染 最终状态,即 enter-to 类所指向的状态。

requestAnimationFrame 实现下一帧的变动

window.requestAnimationFrame(callback) 会在浏览器在 下次重绘之前 调用指定的 回调函数 用于更新动画。

也就是说,单个的 requestAnimationFrame() 办法是在 以后帧 中执行的,也就是如果想要在 下一帧 中执行就须要应用两个 requestAnimationFrame() 办法嵌套的形式来实现,如下:

  // 嵌套的 requestAnimationFrame 实现在下一帧中,切换元素状态
  requestAnimationFrame(() => {requestAnimationFrame(() => {div.classList.remove("enter-from");
      div.classList.add("enter-to");
    });
  });

transitionend 事件监听动效完结

以上就实现元素的 进入动效 ,那么在动效完结之后,别忘了将本来和 进入动效 相干的 移除掉,能够通过 transitionend 事件 监听动效是否完结,如下

// 嵌套的 requestAnimationFrame 实现在下一帧中,切换元素状态
  requestAnimationFrame(() => {requestAnimationFrame(() => {div.classList.remove("enter-from");
      div.classList.add("enter-to");

      // 动效完结后,移除和动效相干的类
      div.addEventListener("transitionend", () => {div.classList.remove("enter-to");
        div.classList.remove("enter-active");
      });
    });
  });

以上就是 进场动效 的实现,如下:

离场动效

有了进场动效的实现过程,在定义 离场动效 时就能够抉择和 进场动效 绝对应的模式,即 初始状态 过渡过程 完结状态

用 CSS 形容

   // 初始状态
  .leave-from {transform: translateY(0);
  }
  // 过渡状态
  .leave-active {transition: transform 2s ease-out;}
  // 完结状态
  .leave-to {transform: translateY(-300px);
  }

用 JavaScript 形容

所谓的 离场 就是指 DOM 元素 卸载 ,但因为要有离场动效要展现,所以不能间接卸载对应的元素,而是要 期待离场动效完结之后在进行卸载

为了直观一些,咱们能够增加一个离场的按钮,用于触发离场动效。

  // 创立离场按钮
  const btn = document.createElement("button");
  btn.innerText = "离场";
  document.body.appendChild(btn);

  // 绑定事件
  btn.addEventListener("click", () => {
    // 设置离场 初始状态 和 静止过程
    div.classList.add("leave-from");
    div.classList.add("leave-active");

    // 嵌套的 requestAnimationFrame 实现在下一帧中,切换元素状态
    requestAnimationFrame(() => {requestAnimationFrame(() => {div.classList.remove("leave-from");
        div.classList.add("leave-to");

        // 动效完结后,移除和动效相干的类
        div.addEventListener("transitionend", () => {div.classList.remove("leave-to");
          div.classList.remove("leave-active");

          // 离场动效完结,移除指标元素
          div.remove();});
      });
    });
  });

离场动效,如下:

实现 Transition 组件

以上的实现过程,能够将其进行抽象化为三个阶段:

  • beforeEnter
  • enter
  • leave

当初要从 命令式编程 转向 申明式编程 了,因为咱们要去编写 Vue 组件 了,即基于 VNode 节点来实现,为了和一般的 VNode 作为辨别,Vue 中会为指标元素的 VNode 节点上增加 transition 属性:

  • Transition 组件 自身不会渲染任何额定的内容,它只是通过 默认插槽 读取过渡元素,并渲染须要过渡的元素
  • Transition 组件 作用,是在过渡元素的 VNode 节点上增加和 transition 相干的 钩子函数
<script lang="ts">
import {defineComponent} from 'vue';

const nextFrame = (callback: () => unknown) => {requestAnimationFrame(() => {requestAnimationFrame(callback)
  })
}

export default defineComponent({
  name: 'Transition',
  setup(props, { slots}) {
    // 返回 render 函数
    return () => {
      // 通过默认插槽,获取指标元素
      const innerVNode = (slots as any).default()

      // 为指标元素增加 transition 相干钩子
      innerVNode.transition = {beforeEnter(el: any) {console.log(111)
          // 设置 初始状态 和 静止过程
          el.classList.add("enter-from");
          el.classList.add("enter-active");
        },
        enter(el: any) {
          // 在下一帧切换状态
          nextFrame(() => {
            // 切换状态
            el.classList.remove("enter-from");
            el.classList.add("enter-to");

            // 动效完结后,移除和动效相干的类
            el.addEventListener("transitionend", () => {el.classList.remove("enter-to");
              el.classList.remove("enter-active");
            });
          })
        },
        leave(el: any) {
          // 设置离场 初始状态 和 静止过程
          el.classList.add("leave-from");
          el.classList.add("leave-active");

          // 在下一帧中,切换元素状态
          nextFrame(() => {
            // 切换元素状态
            el.classList.remove("leave-from");
            el.classList.add("leave-to");

            // 动效完结后,移除和动效相干的类
            el.addEventListener("transitionend", () => {el.classList.remove("leave-to");
              el.classList.remove("leave-active");

              // 离场动效完结,移除指标元素
              el.remove();});
          })
        }
      }

      // 返回批改过的 VNode
      return innerVNode
    }
  }
})
</script>

最初

欢送关注同名公众号《熊的猫》,文章会同步更新!

从整体来看,Transition 组件 的外围并不算简单,特地是以 命令式编程 实现之后,但话说回来在 Vue 源码中实现的还是很全面的,比方:

  • 提供 props 实现用户自定义类名
  • 提供 内置模式,即先进后出(in-out)、后进先出(enter-to
  • 反对 v-show 形式触发过渡成果

心愿以上内容对你有所帮忙!!!

退出移动版