Vue 3.0 進(jìn)入過渡&離開過渡

2021-07-16 11:32 更新

在插入、更新或從 DOM 中移除項(xiàng)時(shí),Vue 提供了多種應(yīng)用轉(zhuǎn)換效果的方法。這包括以下工具:

  • 自動(dòng)為 CSS 轉(zhuǎn)換和動(dòng)畫應(yīng)用 class;
  • 集成第三方 CSS 動(dòng)畫庫,例如 animate.css
  • 在過渡鉤子期間使用 JavaScript 直接操作 DOM;
  • 集成第三方 JavaScript 動(dòng)畫庫。

在這里,我們只會(huì)講到進(jìn)入、離開和列表的過渡,你也可以看下一節(jié)的管理過渡狀態(tài) 。

#單元素/組件的過渡

Vue 提供了 transition 的封裝組件,在下列情形中,可以給任何元素和組件添加進(jìn)入/離開過渡

  • 條件渲染 (使用 v-if)
  • 條件展示 (使用 v-show)
  • 動(dòng)態(tài)組件
  • 組件根節(jié)點(diǎn)

這里是一個(gè)典型的例子:

  1. <div id="demo">
  2. <button @click="show = !show">
  3. Toggle
  4. </button>
  5. <transition name="fade">
  6. <p v-if="show">hello</p>
  7. </transition>
  8. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. show: true
  5. }
  6. }
  7. }
  8. Vue.createApp(Demo).mount('#demo')

  1. .fade-enter-active,
  2. .fade-leave-active {
  3. transition: opacity 0.5s ease;
  4. }
  5. .fade-enter-from,
  6. .fade-leave-to {
  7. opacity: 0;
  8. }

點(diǎn)擊此處實(shí)現(xiàn)

當(dāng)插入或刪除包含在 transition 組件中的元素時(shí),Vue 將會(huì)做以下處理:

  1. 自動(dòng)嗅探目標(biāo)元素是否應(yīng)用了 CSS 過渡或動(dòng)畫,如果是,在恰當(dāng)?shù)臅r(shí)機(jī)添加/刪除 CSS 類名。
  2. 如果過渡組件提供了 JavaScript 鉤子函數(shù) ,這些鉤子函數(shù)將在恰當(dāng)?shù)臅r(shí)機(jī)被調(diào)用。
  3. 如果沒有找到 JavaScript 鉤子并且也沒有檢測(cè)到 CSS 過渡/動(dòng)畫,DOM 操作 (插入/刪除) 在下一幀中立即執(zhí)行。(注意:此指瀏覽器逐幀動(dòng)畫機(jī)制,和 Vue 的 nextTick 概念不同)

#過渡class

在進(jìn)入/離開的過渡中,會(huì)有 6 個(gè) class 切換。

  1. v-enter-from:定義進(jìn)入過渡的開始狀態(tài)。在元素被插入之前生效,在元素被插入之后的下一幀移除。
  2. v-enter-active:定義進(jìn)入過渡生效時(shí)的狀態(tài)。在整個(gè)進(jìn)入過渡的階段中應(yīng)用,在元素被插入之前生效,在過渡/動(dòng)畫完成之后移除。這個(gè)類可以被用來定義進(jìn)入過渡的過程時(shí)間,延遲和曲線函數(shù)。
  3. v-enter-to:定義進(jìn)入過渡的結(jié)束狀態(tài)。在元素被插入之后下一幀生效 (與此同時(shí) v-enter-from 被移除),在過渡/動(dòng)畫完成之后移除。
  4. v-leave-from:定義離開過渡的開始狀態(tài)。在離開過渡被觸發(fā)時(shí)立刻生效,下一幀被移除。
  5. v-leave-active:定義離開過渡生效時(shí)的狀態(tài)。在整個(gè)離開過渡的階段中應(yīng)用,在離開過渡被觸發(fā)時(shí)立刻生效,在過渡/動(dòng)畫完成之后移除。這個(gè)類可以被用來定義離開過渡的過程時(shí)間,延遲和曲線函數(shù)。
  6. v-leave-to:離開過渡的結(jié)束狀態(tài)。在離開過渡被觸發(fā)之后下一幀生效 (與此同時(shí) v-leave-from 被刪除),在過渡/動(dòng)畫完成之后移除。

對(duì)于這些在過渡中切換的類名來說,如果你使用一個(gè)沒有名字的 <transition>,則 v- 是這些class名的默認(rèn)前綴。如果你使用了 <transition name="my-transition">,那么 v-enter-from會(huì)替換為 my-transition-enter-from。

v-enter-activev-leave-active 可以控制進(jìn)入/離開過渡的不同的緩和曲線,在下面章節(jié)會(huì)有個(gè)示例說明。

#CSS 過渡

常用的過渡都是使用 CSS 過渡。

  1. <div id="demo">
  2. <button @click="show = !show">
  3. Toggle render
  4. </button>
  5. <transition name="slide-fade">
  6. <p v-if="show">hello</p>
  7. </transition>
  8. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. show: true
  5. }
  6. }
  7. }
  8. Vue.createApp(Demo).mount('#demo')

  1. /* 可以設(shè)置不同的進(jìn)入和離開動(dòng)畫 */
  2. /* 設(shè)置持續(xù)時(shí)間和動(dòng)畫函數(shù) */
  3. .slide-fade-enter-active {
  4. transition: all 0.3s ease-out;
  5. }
  6. .slide-fade-leave-active {
  7. transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
  8. }
  9. .slide-fade-enter-from,
  10. .slide-fade-leave-to {
  11. transform: translateX(20px);
  12. opacity: 0;
  13. }

點(diǎn)擊此處實(shí)現(xiàn)

#CSS 動(dòng)畫

CSS 動(dòng)畫用法同 CSS 過渡,區(qū)別是在動(dòng)畫中 v-enter-from 類名在節(jié)點(diǎn)插入 DOM 后不會(huì)立即刪除,而是在 animationend 事件觸發(fā)時(shí)刪除。

下面是一個(gè)例子,為了簡潔起見,省略了帶前綴的 CSS 規(guī)則:

  1. <div id="demo">
  2. <button @click="show = !show">Toggle show</button>
  3. <transition name="bounce">
  4. <p v-if="show">
  5. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis
  6. enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi
  7. tristique senectus et netus.
  8. </p>
  9. </transition>
  10. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. show: true
  5. }
  6. }
  7. }
  8. Vue.createApp(Demo).mount('#demo')

  1. .bounce-enter-active {
  2. animation: bounce-in 0.5s;
  3. }
  4. .bounce-leave-active {
  5. animation: bounce-in 0.5s reverse;
  6. }
  7. @keyframes bounce-in {
  8. 0% {
  9. transform: scale(0);
  10. }
  11. 50% {
  12. transform: scale(1.25);
  13. }
  14. 100% {
  15. transform: scale(1);
  16. }
  17. }

點(diǎn)擊此處實(shí)現(xiàn)

#自定義過渡 class 類名

我們可以通過以下 attribute 來自定義過渡類名:

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

他們的優(yōu)先級(jí)高于普通的類名,這對(duì)于 Vue 的過渡系統(tǒng)和其他第三方 CSS 動(dòng)畫庫,如 Animate.css. 結(jié)合使用十分有用。

示例:

  1. <link
  2. rel="external nofollow" target="_blank"
  3. rel="stylesheet"
  4. type="text/css"
  5. />
  6. <div id="demo">
  7. <button @click="show = !show">
  8. Toggle render
  9. </button>
  10. <transition
  11. name="custom-classes-transition"
  12. enter-active-class="animate__animated animate__tada"
  13. leave-active-class="animate__animated animate__bounceOutRight"
  14. >
  15. <p v-if="show">hello</p>
  16. </transition>
  17. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. show: true
  5. }
  6. }
  7. }
  8. Vue.createApp(Demo).mount('#demo')

#同時(shí)使用過渡和動(dòng)畫

Vue 為了知道過渡的完成,必須設(shè)置相應(yīng)的事件監(jiān)聽器。它可以是 transitionendanimationend,這取決于給元素應(yīng)用的 CSS 規(guī)則。如果你使用其中任何一種,Vue 能自動(dòng)識(shí)別類型并設(shè)置監(jiān)聽。

但是,在一些場(chǎng)景中,你需要給同一個(gè)元素同時(shí)設(shè)置兩種過渡動(dòng)效,比如 animation 很快的被觸發(fā)并完成了,而 transition 效果還沒結(jié)束。在這種情況中,你就需要使用 type attribute 并設(shè)置 animationtransition 來明確聲明你需要 Vue 監(jiān)聽的類型。

#顯性的過渡持續(xù)時(shí)間

在很多情況下,Vue 可以自動(dòng)得出過渡效果的完成時(shí)機(jī)。默認(rèn)情況下,Vue 會(huì)等待其在過渡效果的根元素的第一個(gè) transitionendanimationend 事件。然而也可以不這樣設(shè)定——比如,我們可以擁有一個(gè)精心編排的一系列過渡效果,其中一些嵌套的內(nèi)部元素相比于過渡效果的根元素有延遲的或更長的過渡效果。

在這種情況下你可以用 <transition> 組件上的 duration prop 定制一個(gè)顯性的過渡持續(xù)時(shí)間 (以毫秒計(jì)):

  1. <transition :duration="1000">...</transition>

你也可以定制進(jìn)入和移出的持續(xù)時(shí)間:

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

#JavaScript 鉤子

可以在 attribute 中聲明 JavaScript 鉤子

  1. <transition
  2. @before-enter="beforeEnter"
  3. @enter="enter"
  4. @after-enter="afterEnter"
  5. @enter-cancelled="enterCancelled"
  6. @before-leave="beforeLeave"
  7. @leave="leave"
  8. @after-leave="afterLeave"
  9. @leave-cancelled="leaveCancelled"
  10. :css="false"
  11. >
  12. <!-- ... -->
  13. </transition>

  1. // ...
  2. methods: {
  3. // --------
  4. // ENTERING
  5. // --------
  6. beforeEnter(el) {
  7. // ...
  8. },
  9. // 當(dāng)與 CSS 結(jié)合使用時(shí)
  10. // 回調(diào)函數(shù) done 是可選的
  11. enter(el, done) {
  12. // ...
  13. done()
  14. },
  15. afterEnter(el) {
  16. // ...
  17. },
  18. enterCancelled(el) {
  19. // ...
  20. },
  21. // --------
  22. // 離開時(shí)
  23. // --------
  24. beforeLeave(el) {
  25. // ...
  26. },
  27. // 當(dāng)與 CSS 結(jié)合使用時(shí)
  28. // 回調(diào)函數(shù) done 是可選的
  29. leave(el, done) {
  30. // ...
  31. done()
  32. },
  33. afterLeave(el) {
  34. // ...
  35. },
  36. // leaveCancelled 只用于 v-show 中
  37. leaveCancelled(el) {
  38. // ...
  39. }
  40. }

這些鉤子函數(shù)可以結(jié)合 CSS transitions/animations 使用,也可以單獨(dú)使用。

當(dāng)只用 JavaScript 過渡的時(shí)候,在 enterleave 鉤中必須使用 done 進(jìn)行回調(diào)。否則,它們將被同步調(diào)用,過渡會(huì)立即完成。添加 :css="false",也會(huì)讓 Vue 會(huì)跳過 CSS 的檢測(cè),除了性能略高之外,這可以避免過渡過程中 CSS 規(guī)則的影響。

現(xiàn)在讓我們來看一個(gè)例子。下面是一個(gè)使用 GreenSock 的 JavaScript 過渡:

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js" rel="external nofollow" ></script>
  2. <div id="demo">
  3. <button @click="show = !show">
  4. Toggle
  5. </button>
  6. <transition
  7. @before-enter="beforeEnter"
  8. @enter="enter"
  9. @leave="leave"
  10. :css="false"
  11. >
  12. <p v-if="show">
  13. Demo
  14. </p>
  15. </transition>
  16. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. show: false
  5. }
  6. },
  7. methods: {
  8. beforeEnter(el) {
  9. gsap.set(el, {
  10. scaleX: 0.8,
  11. scaleY: 1.2
  12. })
  13. },
  14. enter(el, done) {
  15. gsap.to(el, {
  16. duration: 1,
  17. scaleX: 1.5,
  18. scaleY: 0.7,
  19. opacity: 1,
  20. x: 150,
  21. ease: 'elastic.inOut(2.5, 1)',
  22. onComplete: done
  23. })
  24. },
  25. leave(el, done) {
  26. gsap.to(el, {
  27. duration: 0.7,
  28. scaleX: 1,
  29. scaleY: 1,
  30. x: 300,
  31. ease: 'elastic.inOut(2.5, 1)'
  32. })
  33. gsap.to(el, {
  34. duration: 0.2,
  35. delay: 0.5,
  36. opacity: 0,
  37. onComplete: done
  38. })
  39. }
  40. }
  41. }
  42. Vue.createApp(Demo).mount('#demo')

點(diǎn)擊此處實(shí)現(xiàn)

#初始渲染的過渡

可以通過 appear attribute 設(shè)置節(jié)點(diǎn)在初始渲染的過渡

  1. <transition appear>
  2. <!-- ... -->
  3. </transition>

#多個(gè)元素的過渡

我們之后討論多個(gè)組件的過渡,對(duì)于原生標(biāo)簽可以使用 v-if/v-else。最常見的多標(biāo)簽過渡是一個(gè)列表和描述這個(gè)列表為空消息的元素:

  1. <transition>
  2. <table v-if="items.length > 0">
  3. <!-- ... -->
  4. </table>
  5. <p v-else>Sorry, no items found.</p>
  6. </transition>

實(shí)際上,通過使用多個(gè) v-if 或?qū)蝹€(gè)元素綁定到一個(gè)動(dòng)態(tài) property,可以在任意數(shù)量的元素之間進(jìn)行過渡。例如:

  1. <transition>
  2. <button v-if="docState === 'saved'" key="saved">
  3. Edit
  4. </button>
  5. <button v-if="docState === 'edited'" key="edited">
  6. Save
  7. </button>
  8. <button v-if="docState === 'editing'" key="editing">
  9. Cancel
  10. </button>
  11. </transition>

也可以寫為:

  1. <transition>
  2. <button :key="docState">
  3. {{ buttonMessage }}
  4. </button>
  5. </transition>

  1. // ...
  2. computed: {
  3. buttonMessage() {
  4. switch (this.docState) {
  5. case 'saved': return 'Edit'
  6. case 'edited': return 'Save'
  7. case 'editing': return 'Cancel'
  8. }
  9. }
  10. }

#過渡模式

這里還有一個(gè)問題,試著點(diǎn)擊下面的按鈕:

點(diǎn)擊此處實(shí)現(xiàn)

在“on”按鈕和“off”按鈕的過渡中,兩個(gè)按鈕都被重繪了,一個(gè)離開過渡的時(shí)候另一個(gè)開始進(jìn)入過渡。這是 <transition> 的默認(rèn)行為 —— 進(jìn)入和離開同時(shí)發(fā)生。

有時(shí)這很有效,例如當(dāng)過渡項(xiàng)絕對(duì)位于彼此的 top 時(shí):

點(diǎn)擊此處實(shí)現(xiàn)

同時(shí)生效的進(jìn)入和離開的過渡不能滿足所有要求,所以 Vue 提供了過渡模式

  • in-out: 新元素先進(jìn)行過渡,完成之后當(dāng)前元素過渡離開。
  • out-in: 當(dāng)前元素先進(jìn)行過渡,完成之后新元素過渡進(jìn)入。

TIP

很快就會(huì)發(fā)現(xiàn) out-in 是你大多數(shù)時(shí)候想要的狀態(tài) ????

現(xiàn)在讓我們用 out-in 更新 on/off 按鈕的轉(zhuǎn)換:

  1. <transition name="fade" mode="out-in">
  2. <!-- ... the buttons ... -->
  3. </transition>

點(diǎn)擊此處實(shí)現(xiàn)

通過添加一個(gè) attribute,我們修復(fù)了原來的過渡,而不必添加任何特殊 style。

我們可以用它來協(xié)調(diào)更具表現(xiàn)力的動(dòng)作,例如折疊卡片,如下所示。實(shí)際上是兩個(gè)元素在彼此之間轉(zhuǎn)換,但是由于開始狀態(tài)和結(jié)束狀態(tài)的比例是相同的:水平為0,它看起來就像一個(gè)流體運(yùn)動(dòng)。這種輕描淡寫對(duì)于真實(shí)的 UI 微交互非常有用:

點(diǎn)擊此處實(shí)現(xiàn)

#多個(gè)組件之間過渡

組件之間的過渡更簡單 —— 我們甚至不需要 key 屬性。相反,我們包裝了一個(gè)動(dòng)態(tài)組件

  1. <div id="demo">
  2. <input v-model="view" type="radio" value="v-a" id="a"><label for="a">A</label>
  3. <input v-model="view" type="radio" value="v-b" id="b"><label for="b">B</label>
  4. <transition name="component-fade" mode="out-in">
  5. <component :is="view"></component>
  6. </transition>
  7. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. view: 'v-a'
  5. }
  6. },
  7. components: {
  8. 'v-a': {
  9. template: '<div>Component A</div>'
  10. },
  11. 'v-b': {
  12. template: '<div>Component B</div>'
  13. }
  14. }
  15. }
  16. Vue.createApp(Demo).mount('#demo')

  1. .component-fade-enter-active,
  2. .component-fade-leave-active {
  3. transition: opacity 0.3s ease;
  4. }
  5. .component-fade-enter-from,
  6. .component-fade-leave-to {
  7. opacity: 0;
  8. }

點(diǎn)擊此處實(shí)現(xiàn)

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)