当前位置 : 主页 > 网络编程 > JavaScript >

彻底搞懂Transition内置组件

来源:互联网 收集:自由互联 发布时间:2023-12-05
目录 前言 Transition 内置组件 触发条件 再分类 六个过渡时机 Transition 组件 CSS transition 属性 核心原理 实现 Transition 组件 原生 DOM 如何实现过渡? 原生 DOM 元素移动示例 进场动效 离场动
目录
  • 前言
  • Transition 内置组件
    • 触发条件
    • 再分类
    • 六个过渡时机
    • Transition 组件 & CSS transition 属性
    • 核心原理
  • 实现 Transition 组件
    • 原生 DOM 如何实现过渡?
    • 原生 DOM 元素移动示例
    • 进场动效
    • 离场动效
    • 实现 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-if 和 v-for 一起使用时,在 Vue2 和 Vue3 中的不同

    • 在 Vue2 中,当它们处于同一节点时,v-for 的优先级比 v-if 更高,即 v-if 将分别重复运行于每个 v-for 循环中,也就是 v-if 可以正常访问 v-for 中的数据
    • 在 Vue3 中,当它们处于同一节点时,v-if 的优先级比 v-for 更高,即此时只要 v-if 的值为 false 则 v-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>

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

    • CSS 的 transition 过渡属性(上述例子使用的方案)
    • CSS 的 animation 动画属性

    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 方式触发过渡效果

    以上就是彻底搞懂Transition内置组件的详细内容,更多关于Transition内置组件的资料请关注自由互联其它相关文章!

    上一篇:vue中watch监听路由传来的参数变化问题
    下一篇:没有了
    网友评论