徹底搞懂Transition內(nèi)置組件
前言
<Transition>
作為一個(gè) Vue 中的內(nèi)置組件,它可以將 進(jìn)入動(dòng)畫(huà) 和 離開(kāi)動(dòng)畫(huà) 應(yīng)用到通過(guò) 默認(rèn)插槽 傳遞給目標(biāo)元素或組件上。
也許你有在使用,但是一直不清楚它的原理或具體實(shí)現(xiàn),甚至不清楚其內(nèi)部提供的各個(gè) class 到底怎么配合使用,想看源碼又被其中各種引入搞得七葷八素...
本篇文章就以 Transition 組件為核心,探討其核心原理的實(shí)現(xiàn),文中不會(huì)對(duì)其各個(gè)屬性再做額外解釋,畢竟這些看文檔就夠了,希望能夠給你帶來(lái)幫助?。?!
Transition 內(nèi)置組件
觸發(fā)條件
<Transition>
組件的 進(jìn)入動(dòng)畫(huà) 或 離開(kāi)動(dòng)畫(huà) 可通過(guò)以下的條件之一觸發(fā):
- 由
v-if
所觸發(fā)的切換 - 由
v-show
所觸發(fā)的切換 - 由特殊元素
<component name="x">
切換的動(dòng)態(tài)組件 - 改變特殊的
key
屬性
再分類
其實(shí)我們可以將以上情況進(jìn)行 再分類:
組件 掛載 和 銷毀
v-if
的變化<component name="x">
的變化key
的變化
組件 樣式 屬性
display: none | x
設(shè)置v-show
的變化
【擴(kuò)展】v-if
和 v-for
一起使用時(shí),在 Vue2
和 Vue3
中的不同
- 在 Vue2 中,當(dāng)它們處于同一節(jié)點(diǎn)時(shí),
v-for
的優(yōu)先級(jí)比v-if
更高,即v-if
將分別重復(fù)運(yùn)行于每個(gè)v-for
循環(huán)中,也就是v-if
可以正常訪問(wèn)v-for
中的數(shù)據(jù) - 在 Vue3 中,當(dāng)它們處于同一節(jié)點(diǎn)時(shí),
v-if
的優(yōu)先級(jí)比v-for
更高,即此時(shí)只要v-if
的值為false
則 v-for 的列表就不會(huì)被渲染,也就是v-if
不能訪問(wèn)到v-for
中的數(shù)據(jù)
六個(gè)過(guò)渡時(shí)機(jī)
總結(jié)起來(lái)就分為 進(jìn)入 和 離開(kāi) 動(dòng)畫(huà)的 初始狀態(tài)、生效狀態(tài)、結(jié)束狀態(tài),具體如下:
v-enter-from
- 進(jìn)入 動(dòng)畫(huà)的 起始狀態(tài)
- 在元素插入之前添加,在元素插入完成后的 下一幀移除
v-enter-active
- 進(jìn)入 動(dòng)畫(huà)的 生效狀態(tài),應(yīng)用于整個(gè)進(jìn)入動(dòng)畫(huà)階段
- 在元素被插入之前添加,在過(guò)渡或動(dòng)畫(huà)完成之后移除
- 這個(gè)
class
可以被用來(lái)定義進(jìn)入動(dòng)畫(huà)的持續(xù)時(shí)間、延遲與速度曲線類型
v-enter-to
- 進(jìn)入 動(dòng)畫(huà)的 結(jié)束狀態(tài)
- 在元素插入完成后的下一幀被添加 (也就是
v-enter-from
被移除的同時(shí)),在過(guò)渡或動(dòng)畫(huà)完成之后移除
v-leave-from
- 離開(kāi) 動(dòng)畫(huà)的 起始狀態(tài)
- 在離開(kāi)過(guò)渡效果被觸發(fā)時(shí)立即添加,在一幀后被移除
v-leave-active
- 離開(kāi) 動(dòng)畫(huà)的 生效狀態(tài),應(yīng)用于整個(gè)離開(kāi)動(dòng)畫(huà)階段
- 在離開(kāi)過(guò)渡效果被觸發(fā)時(shí)立即添加,在 過(guò)渡或動(dòng)畫(huà)完成之后移除
- 這個(gè)
class
可以被用來(lái)定義離開(kāi)動(dòng)畫(huà)的持續(xù)時(shí)間、延遲與速度曲線類型
v-leave-to
- 離開(kāi) 動(dòng)畫(huà)的 結(jié)束狀態(tài)
- 在一個(gè)離開(kāi)動(dòng)畫(huà)被觸發(fā)后的 下一幀 被添加 (即
v-leave-from
被移除的同時(shí)),在 過(guò)渡或動(dòng)畫(huà)完成之后移除
其中的 v
前綴是允許修改的,可以 <Transition>
組件傳一個(gè) name
的 prop
來(lái)聲明一個(gè)過(guò)渡效果名,如下就是將 v
前綴修改為 **`
modal `** 前綴:
<Transition name="modal"> ... </Transition>
Transition 組件 & CSS transition 屬性
以上這個(gè)簡(jiǎn)單的效果,核心就是兩個(gè)時(shí)機(jī):
v-enter-active
進(jìn)入動(dòng)畫(huà)的 生效狀態(tài)v-leave-active
離開(kāi)動(dòng)畫(huà)的 生效狀態(tài)
再配合簡(jiǎn)單的 CSS 過(guò)渡屬性就可以達(dá)到效果,代碼如下:
<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> <!-- 錢(qián)袋子 --> <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>
當(dāng)然動(dòng)畫(huà)的效果是多種多樣的,不僅只是局限于這一種,例如可以配合:
- CSS 的 transition 過(guò)渡屬性(上述例子使用的方案)
- CSS 的 animation 動(dòng)畫(huà)屬性
核心原理
通過(guò)上述內(nèi)容其實(shí)不難發(fā)現(xiàn)其核心原理就是:
- 當(dāng) 組件(DOM) 被 掛載 時(shí),將過(guò)渡動(dòng)效添加到該 DOM 元素上
- 當(dāng) 組件(DOM) 被 卸載 時(shí),不是直接卸載,而是等待附加到 DOM 元素上的 動(dòng)效執(zhí)行完成,然后在真正執(zhí)行卸載操作,即 延遲卸載時(shí)機(jī)
在上述的過(guò)程中,<Transition>
組件會(huì)為 目標(biāo)組件/元素 通過(guò)添加不同的 class
來(lái)定義 初始、生效、結(jié)束 三個(gè)狀態(tài),當(dāng)進(jìn)入下一個(gè)狀態(tài)時(shí)會(huì)把上一個(gè)狀態(tài)對(duì)應(yīng)的 class
移除。
那么你可能會(huì)問(wèn)了,v-show
的形式也不符合 掛載/卸載 的形式呀,畢竟它只是在修改 DOM 元素的 display: none | x
的樣式!
讓源碼中的注釋來(lái)回答:
v-if
、<component name="x">
、key
控制組件 顯示/隱藏 的方式是 掛載/卸載 組件,而 v-show
控制組件 顯示/隱藏 的方式是 修改/重置 display: none | x
屬性值,從本質(zhì)上看方式不同,但從結(jié)果上看都屬于控制組件的 顯示/隱藏,即功能是一致的,而這里所說(shuō)的 掛載/卸載 是針對(duì)大部分情況來(lái)說(shuō)的,畢竟四種觸發(fā)方式中就有三種符合此情況。
實(shí)現(xiàn) Transition 組件
所謂 Transition 組件畢竟是 Vue 的內(nèi)置組件,換句話說(shuō),組件的編寫(xiě)要符合 Vue 的規(guī)范(即 聲明式寫(xiě)法),但為了更好的理解核心原理,我們應(yīng)該從 原生 DOM 的過(guò)渡開(kāi)始(即 命令式寫(xiě)法)探討。
原生 DOM 如何實(shí)現(xiàn)過(guò)渡?
所謂的 過(guò)渡動(dòng)效 本質(zhì)上就是一個(gè) DOM 元素在 兩種狀態(tài)間的轉(zhuǎn)換,瀏覽器 會(huì)根據(jù)我們?cè)O(shè)置的過(guò)渡效果 自行完成 DOM 元素的過(guò)渡。
而 狀態(tài)的轉(zhuǎn)換 指的就是 初始化狀態(tài) 和 結(jié)束狀態(tài) 的轉(zhuǎn)換,并且配合 CSS 中的 transition
屬性就可以實(shí)現(xiàn)兩個(gè)狀態(tài)間的過(guò)渡,即 運(yùn)動(dòng)過(guò)程。
原生 DOM 元素移動(dòng)示例
假設(shè)要為一個(gè)元素在垂直方向上添加進(jìn)場(chǎng)動(dòng)效:從 原始位置 向上移動(dòng) 200px 的位置,然后在 1s 內(nèi)運(yùn)動(dòng)回 原始位置。
進(jìn)場(chǎng)動(dòng)效
用 CSS 描述
// 描述物體 .box { width: 100px; height: 100px; background-color: red; box-shadow: 0 0 8px; border-radius: 50%; } // 初始狀態(tài) .enter-from { transform: translateY(-200px); } // 運(yùn)動(dòng)過(guò)程 .enter-active { transition: transform 1s ease-in-out; } // 結(jié)束狀態(tài) .enter-to { transform: translateY(0); }
用 JavaScript 描述
// 創(chuàng)建元素 const div = document.createElement('div') div.classList.add('box') // 添加 初始狀態(tài) 和 運(yùn)動(dòng)過(guò)程 div.classList.add('enter-from') div.classList.add('enter-active') // 將元素添加到頁(yè)面上 document.body.appendChild(div) // 切換元素狀態(tài) div.classList.remove('enter-from') div.classList.add('enter-to')
從 命令式編程 的步驟上來(lái)看,似乎每一步都沒(méi)有問(wèn)題,但實(shí)際的過(guò)渡動(dòng)畫(huà)是不會(huì)生效的,雖然在代碼中我們有 狀態(tài)的切換,但這個(gè)切換的操作對(duì)于 瀏覽器 來(lái)講是在 同一幀中進(jìn)行的,所以只會(huì)渲染 最終狀態(tài),即 enter-to
類所指向的狀態(tài)。
requestAnimationFrame 實(shí)現(xiàn)下一幀的變化
window.requestAnimationFrame(callback)
會(huì)在瀏覽器在 下次重繪之前 調(diào)用指定的 回調(diào)函數(shù) 用于更新動(dòng)畫(huà)。
也就是說(shuō),單個(gè)的 requestAnimationFrame() 方法是在 當(dāng)前幀 中執(zhí)行的,也就是如果想要在 下一幀 中執(zhí)行就需要使用兩個(gè) requestAnimationFrame() 方法嵌套的方式來(lái)實(shí)現(xiàn),如下:
// 嵌套的 requestAnimationFrame 實(shí)現(xiàn)在下一幀中,切換元素狀態(tài) requestAnimationFrame(() => { requestAnimationFrame(() => { div.classList.remove("enter-from"); div.classList.add("enter-to"); }); });
transitionend 事件監(jiān)聽(tīng)動(dòng)效結(jié)束
以上就完成元素的 進(jìn)入動(dòng)效,那么在動(dòng)效結(jié)束之后,別忘了將原本和 進(jìn)入動(dòng)效 相關(guān)的 類 移除掉,可以通過(guò) transitionend 事件 監(jiān)聽(tīng)動(dòng)效是否結(jié)束,如下
// 嵌套的 requestAnimationFrame 實(shí)現(xiàn)在下一幀中,切換元素狀態(tài) requestAnimationFrame(() => { requestAnimationFrame(() => { div.classList.remove("enter-from"); div.classList.add("enter-to"); // 動(dòng)效結(jié)束后,移除和動(dòng)效相關(guān)的類 div.addEventListener("transitionend", () => { div.classList.remove("enter-to"); div.classList.remove("enter-active"); }); }); });
以上就是 進(jìn)場(chǎng)動(dòng)效
的實(shí)現(xiàn),如下:
離場(chǎng)動(dòng)效
有了進(jìn)場(chǎng)動(dòng)效的實(shí)現(xiàn)過(guò)程,在定義 離場(chǎng)動(dòng)效 時(shí)就可以選擇和 進(jìn)場(chǎng)動(dòng)效 相對(duì)應(yīng)的形式,即 初始狀態(tài)、過(guò)渡過(guò)程、結(jié)束狀態(tài)。
用 CSS 描述
// 初始狀態(tài) .leave-from { transform: translateY(0); } // 過(guò)渡狀態(tài) .leave-active { transition: transform 2s ease-out; } // 結(jié)束狀態(tài) .leave-to { transform: translateY(-300px); }
用 JavaScript 描述
所謂的 離場(chǎng) 就是指 DOM 元素 的 卸載,但因?yàn)橐须x場(chǎng)動(dòng)效要展示,所以不能直接卸載對(duì)應(yīng)的元素,而是要 等待離場(chǎng)動(dòng)效結(jié)束之后在進(jìn)行卸載。
為了直觀一些,我們可以添加一個(gè)離場(chǎng)的按鈕,用于觸發(fā)離場(chǎng)動(dòng)效。
// 創(chuàng)建離場(chǎng)按鈕 const btn = document.createElement("button"); btn.innerText = "離場(chǎng)"; document.body.appendChild(btn); // 綁定事件 btn.addEventListener("click", () => { // 設(shè)置離場(chǎng) 初始狀態(tài) 和 運(yùn)動(dòng)過(guò)程 div.classList.add("leave-from"); div.classList.add("leave-active"); // 嵌套的 requestAnimationFrame 實(shí)現(xiàn)在下一幀中,切換元素狀態(tài) requestAnimationFrame(() => { requestAnimationFrame(() => { div.classList.remove("leave-from"); div.classList.add("leave-to"); // 動(dòng)效結(jié)束后,移除和動(dòng)效相關(guān)的類 div.addEventListener("transitionend", () => { div.classList.remove("leave-to"); div.classList.remove("leave-active"); // 離場(chǎng)動(dòng)效結(jié)束,移除目標(biāo)元素 div.remove(); }); }); }); });
離場(chǎng)動(dòng)效,如下:
實(shí)現(xiàn) Transition 組件
以上的實(shí)現(xiàn)過(guò)程,可以將其進(jìn)行抽象化為三個(gè)階段:
- beforeEnter
- enter
- leave
現(xiàn)在要從 命令式編程 轉(zhuǎn)向 聲明式編程 了,因?yàn)槲覀円ゾ帉?xiě) Vue 組件 了,即基于 VNode 節(jié)點(diǎn)來(lái)實(shí)現(xiàn),為了和普通的 VNode 作為區(qū)分,Vue 中會(huì)為目標(biāo)元素的 VNode 節(jié)點(diǎn)上添加 transition 屬性:
Transition 組件
本身不會(huì)渲染任何額外的內(nèi)容,它只是通過(guò)默認(rèn)插槽
讀取過(guò)渡元素,并渲染需要過(guò)渡的元素Transition 組件
作用,是在過(guò)渡元素的VNode
節(jié)點(diǎn)上添加和transition
相關(guān)的鉤子函數(shù)
<script lang="ts"> import { defineComponent } from 'vue'; const nextFrame = (callback: () => unknown) => { requestAnimationFrame(() => { requestAnimationFrame(callback) }) } export default defineComponent({ name: 'Transition', setup(props, { slots }) { // 返回 render 函數(shù) return () => { // 通過(guò)默認(rèn)插槽,獲取目標(biāo)元素 const innerVNode = (slots as any).default() // 為目標(biāo)元素添加 transition 相關(guān)鉤子 innerVNode.transition = { beforeEnter(el: any) { console.log(111) // 設(shè)置 初始狀態(tài) 和 運(yùn)動(dòng)過(guò)程 el.classList.add("enter-from"); el.classList.add("enter-active"); }, enter(el: any) { // 在下一幀切換狀態(tài) nextFrame(() => { // 切換狀態(tài) el.classList.remove("enter-from"); el.classList.add("enter-to"); // 動(dòng)效結(jié)束后,移除和動(dòng)效相關(guān)的類 el.addEventListener("transitionend", () => { el.classList.remove("enter-to"); el.classList.remove("enter-active"); }); }) }, leave(el: any) { // 設(shè)置離場(chǎng) 初始狀態(tài) 和 運(yùn)動(dòng)過(guò)程 el.classList.add("leave-from"); el.classList.add("leave-active"); // 在下一幀中,切換元素狀態(tài) nextFrame(() => { // 切換元素狀態(tài) el.classList.remove("leave-from"); el.classList.add("leave-to"); // 動(dòng)效結(jié)束后,移除和動(dòng)效相關(guān)的類 el.addEventListener("transitionend", () => { el.classList.remove("leave-to"); el.classList.remove("leave-active"); // 離場(chǎng)動(dòng)效結(jié)束,移除目標(biāo)元素 el.remove(); }); }) } } // 返回修改過(guò)的 VNode return innerVNode } } }) </script>
最后
從整體來(lái)看,Transition 組件 的核心并不算復(fù)雜,特別是以 命令式編程 實(shí)現(xiàn)之后,但話說(shuō)回來(lái)在 Vue 源碼中實(shí)現(xiàn)的還是很全面的,比如:
- 提供
props
實(shí)現(xiàn)用戶自定義類名 - 提供 內(nèi)置模式,即先進(jìn)后出(
in-out
)、后進(jìn)先出(enter-to
) - 支持 v-show 方式觸發(fā)過(guò)渡效果
以上就是徹底搞懂Transition內(nèi)置組件的詳細(xì)內(nèi)容,更多關(guān)于Transition內(nèi)置組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Vue?transition組件簡(jiǎn)單實(shí)現(xiàn)數(shù)字滾動(dòng)
- Vue transition過(guò)渡組件詳解
- Vue中transition標(biāo)簽的基本使用教程
- Flutter利用SizeTransition實(shí)現(xiàn)組件飛入效果
- Vue中transition單個(gè)節(jié)點(diǎn)過(guò)渡與transition-group列表過(guò)渡全過(guò)程
- vue中transition組件在項(xiàng)目中運(yùn)用小結(jié)
- Vue transition實(shí)現(xiàn)點(diǎn)贊動(dòng)畫(huà)效果的示例
相關(guān)文章
在 Vue 3 中設(shè)置 `@` 指向根目錄的幾種常見(jiàn)方法匯總
在 Vue 3 項(xiàng)目開(kāi)發(fā)中,為了方便管理和引用文件路徑,設(shè)置 @ 指向根目錄是一項(xiàng)常見(jiàn)的需求,下面給大家分享在Vue3中設(shè)置 `@` 指向根目錄的方法匯總,感興趣的朋友一起看看吧2024-06-06Vue2?this?能夠直接獲取到?data?和?methods?的原理分析
這篇文章主要介紹了Vue2?this能夠直接獲取到data和methods的原理分析,因?yàn)閙ethods里的方法通過(guò)bind指定了this為new?Vue的實(shí)例2022-06-06教你如何開(kāi)發(fā)Vite3插件構(gòu)建Electron開(kāi)發(fā)環(huán)境
這篇文章主要介紹了如何開(kāi)發(fā)Vite3插件構(gòu)建Electron開(kāi)發(fā)環(huán)境,文中給大家提到了如何讓 Vite 加載 Electron 的內(nèi)置模塊和 Node.js 的內(nèi)置模塊,需要的朋友可以參考下2022-11-11如何在Vue3和Vite項(xiàng)目中用SQLite數(shù)據(jù)庫(kù)進(jìn)行數(shù)據(jù)存儲(chǔ)
SQLite是一種嵌入式關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),是一個(gè)零配置、無(wú)服務(wù)器的、自給自足的、事務(wù)性的SQL數(shù)據(jù)庫(kù)引擎,這篇文章主要給大家介紹了關(guān)于如何在Vue3和Vite項(xiàng)目中用SQLite數(shù)據(jù)庫(kù)進(jìn)行數(shù)據(jù)存儲(chǔ)的相關(guān)資料,需要的朋友可以參考下2024-03-03vue keep-alive多層級(jí)路由支持問(wèn)題分析
這篇文章主要介紹了vue keep-alive多層級(jí)路由支持,在文章開(kāi)頭給大家介紹了keep-alive使用問(wèn)題,解決使用keep-alive include屬性問(wèn)題,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03基于Vue2實(shí)現(xiàn)動(dòng)態(tài)折扣表格
這篇文章主要為大家詳細(xì)介紹了如何基于Vue2實(shí)現(xiàn)動(dòng)態(tài)折扣表格,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01Vue項(xiàng)目引入translate.js國(guó)際化自動(dòng)翻譯組件的方法
這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目引入translate.js國(guó)際化自動(dòng)翻譯組件的相關(guān)資料,除了基本的文本翻譯功能之外,jstranslate還提供了一些高級(jí)功能,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01vue?頂部消息橫向滾動(dòng)通知效果實(shí)現(xiàn)
系統(tǒng)頂部展示一個(gè)橫向滾動(dòng)的消息通知,就是消息內(nèi)容從右往左一直滾動(dòng),這篇文章主要介紹了vue頂部消息橫向滾動(dòng)通知,需要的朋友可以參考下2024-02-02vue在自定義組件中使用v-model進(jìn)行數(shù)據(jù)綁定的方法
這篇文章主要介紹了vue在自定義組件中使用v-model進(jìn)行數(shù)據(jù)綁定的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03