目錄
- 前言
- 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="http://news.558idc.com/assets/golden.jpg" /> </div> </transition> </div> <!-- 錢袋子 --> <img class="purse" @click="show = !show" src="http://news.558idc.com/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內置組件的資料請關注技圈網其它相關文章!