跳轉到內容

過渡

Vue提供兩個內建元件,可以幫助處理響應狀態變化時的過渡和動畫

  • <Transition> 在元素或元件進入和離開DOM時應用動畫。這在本頁上有介紹。

  • <TransitionGroup> 在元素或元件被插入到、從v-for列表中移除或在其內部移動時應用動畫。這將在下一章中介紹。

除了這兩個元件之外,我們還可以使用其他技術,如切換CSS類或透過樣式繫結實現狀態驅動的動畫,在Vue中應用動畫。這些額外的技術將在動畫技巧章節中介紹。

<Transition> 元件

<Transition> 是一個內建元件:這意味著它可以在任何元件的模板中使用,而無需註冊。它可以用於對其預設插槽傳入的元素或元件應用進入和離開動畫。進入或離開可以由以下之一觸發

  • 透過 v-if 進行條件渲染
  • 透過 v-show 進行條件顯示
  • 透過 <component> 特殊元素動態切換元件
  • 更改特殊的 key 屬性

這是一個最基本使用示例

模板
<button @click="show = !show">Toggle</button>
<Transition>
  <p v-if="show">hello</p>
</Transition>
CSS
/* we will explain what these classes do next! */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

hello

提示

<Transition> 只支援單個元素或元件作為其插槽內容。如果內容是元件,該元件也必須只有一個根元素。

<Transition> 元件中的元素被插入或移除時,會發生以下情況

  1. Vue會自動檢查目標元素是否應用了CSS過渡或動畫。如果應用了,將新增/移除一系列CSS過渡類,時機適當。

  2. 如果有監聽器監聽JavaScript鉤子,則這些鉤子將在適當的時機被呼叫。

  3. 如果沒有檢測到CSS過渡/動畫,並且沒有提供JavaScript鉤子,則插入和/或刪除的DOM操作將在瀏覽器的下一個動畫幀上執行。

基於CSS的過渡

過渡類

共有六個類用於進入/離開過渡。

Transition Diagram

  1. v-enter-from:進入的起始狀態。在元素插入前新增,元素插入後一幀移除。

  2. v-enter-active:進入的活躍狀態。在整個進入階段應用。在元素插入前新增,在過渡/動畫完成後移除。這個類可以用來定義進入過渡的持續時間、延遲和緩動曲線。

  3. v-enter-to:進入的結束狀態。在元素插入後一幀新增(同時移除v-enter-from),在過渡/動畫完成後移除。

  4. v-leave-from:離開的起始狀態。在離開過渡觸發時立即新增,一幀後移除。

  5. v-leave-active:離開的活躍狀態。在整個離開階段應用。在離開過渡觸發時立即新增,在過渡/動畫完成後移除。這個類可以用來定義離開過渡的持續時間、延遲和緩動曲線。

  6. v-leave-to:離開的結束狀態。在離開過渡觸發後一幀新增(同時移除v-leave-from),在過渡/動畫完成後移除。

v-enter-activev-leave-active允許我們為進入/離開過渡指定不同的緩動曲線,以下章節將給出一個示例。

命名過渡

可以透過name屬性命名一個過渡。

模板
<Transition name="fade">
  ...
</Transition>

對於命名過渡,其過渡類將以其名稱為字首而不是v。例如,上述過渡應用的是fade-enter-active而不是v-enter-active。fade過渡的CSS應如下所示

CSS
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

CSS過渡

<Transition>通常與原生CSS過渡結合使用,如上述基本示例所示。CSS的transition屬性是一個簡寫,允許我們指定過渡的多個方面,包括應動畫化的屬性、過渡的持續時間以及緩動曲線

以下是一個更高階的示例,它轉換多個屬性,為進入和離開指定不同的持續時間緩動曲線。

模板
<Transition name="slide-fade">
  <p v-if="show">hello</p>
</Transition>
CSS
/*
  Enter and leave animations can use different
  durations and timing functions.
*/
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}

hello

CSS動畫

原生CSS動畫的應用方式與CSS過渡相同,區別在於*-enter-from不是在元素插入後立即移除,而是在animationend事件發生時移除。

對於大多數CSS動畫,我們可以在*-enter-active*-leave-active類下簡單宣告它們。以下是一個示例

模板
<Transition name="bounce">
  <p v-if="show" style="text-align: center;">
    Hello here is some bouncy text!
  </p>
</Transition>
CSS
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}

你好,這裡有一些彈跳文字!

自定義過渡類

您還可以透過向<Transition>傳遞以下屬性來指定自定義過渡類

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

這些將覆蓋常規類名。當您想將Vue的過渡系統與現有的CSS動畫庫(如Animate.css)結合使用時,這特別有用。

模板
<!-- assuming Animate.css is included on the page -->
<Transition
  name="custom-classes"
  enter-active-class="animate__animated animate__tada"
  leave-active-class="animate__animated animate__bounceOutRight"
>
  <p v-if="show">hello</p>
</Transition>

同時使用過渡和動畫

Vue需要附加事件監聽器以瞭解何時過渡結束。它可以是被應用的CSS規則的型別決定的transitionendanimationend。如果您只使用其中之一,Vue可以自動檢測正確的型別。

但是,在某些情況下,您可能希望在同一個元素上同時使用這兩種效果,例如,當Vue觸發CSS動畫時,在懸停時新增CSS過渡效果。在這些情況下,您必須透過傳遞具有值為animationtransitiontype屬性來顯式宣告Vue應該關注的型別。

模板
<Transition type="animation">...</Transition>

巢狀過渡和顯式過渡持續時間

儘管過渡類只應用於<Transition>中的直接子元素,但我們可以使用巢狀CSS選擇器來過渡巢狀元素

模板
<Transition name="nested">
  <div v-if="show" class="outer">
    <div class="inner">
      Hello
    </div>
  </div>
</Transition>
CSS
/* rules that target nested elements */
.nested-enter-active .inner,
.nested-leave-active .inner {
  transition: all 0.3s ease-in-out;
}

.nested-enter-from .inner,
.nested-leave-to .inner {
  transform: translateX(30px);
  opacity: 0;
}

/* ... other necessary CSS omitted */

我們甚至可以在進入時為巢狀元素新增過渡延遲,這會建立一個交錯進入動畫序列

CSS
/* delay enter of nested element for staggered effect */
.nested-enter-active .inner {
  transition-delay: 0.25s;
}

然而,這會引發一個小問題。預設情況下,<Transition>元件嘗試透過監聽根過渡元素的第一個transitionendanimationend事件來自動確定何時過渡完成。對於巢狀過渡,所需的操作應該是等待所有內部元素的過渡都完成。

在這種情況下,您可以使用<transition>元件上的duration屬性(以毫秒為單位)指定顯式過渡持續時間。總持續時間應等於延遲加上內部元素的過渡持續時間

模板
<Transition :duration="550">...</Transition>
你好

在Playground中嘗試

如果需要,您還可以使用物件指定進入和離開的持續時間

模板
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>

效能考慮因素

您可能已經注意到上述動畫大多使用transformopacity等屬性。這些屬性易於動畫化,因為

  1. 它們在動畫期間不影響文件佈局,因此它們不會在每一幀上觸發昂貴的CSS佈局計算。

  2. 大多數現代瀏覽器在動畫transform時可以利用GPU硬體加速。

相比之下,heightmargin等屬性將觸發CSS佈局,因此它們動畫化起來成本更高,應謹慎使用。

JavaScript鉤子

您可以透過監聽<Transition>元件上的事件來使用JavaScript掛鉤到過渡過程

html
<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  <!-- ... -->
</Transition>
js
// called before the element is inserted into the DOM.
// use this to set the "enter-from" state of the element
function onBeforeEnter(el) {}

// called one frame after the element is inserted.
// use this to start the entering animation.
function onEnter(el, done) {
  // call the done callback to indicate transition end
  // optional if used in combination with CSS
  done()
}

// called when the enter transition has finished.
function onAfterEnter(el) {}

// called when the enter transition is cancelled before completion.
function onEnterCancelled(el) {}

// called before the leave hook.
// Most of the time, you should just use the leave hook
function onBeforeLeave(el) {}

// called when the leave transition starts.
// use this to start the leaving animation.
function onLeave(el, done) {
  // call the done callback to indicate transition end
  // optional if used in combination with CSS
  done()
}

// called when the leave transition has finished and the
// element has been removed from the DOM.
function onAfterLeave(el) {}

// only available with v-show transitions
function onLeaveCancelled(el) {}
js
export default {
  // ...
  methods: {
    // called before the element is inserted into the DOM.
    // use this to set the "enter-from" state of the element
    onBeforeEnter(el) {},

    // called one frame after the element is inserted.
    // use this to start the animation.
    onEnter(el, done) {
      // call the done callback to indicate transition end
      // optional if used in combination with CSS
      done()
    },

    // called when the enter transition has finished.
    onAfterEnter(el) {},

    // called when the enter transition is cancelled before completion.
    onEnterCancelled(el) {},

    // called before the leave hook.
    // Most of the time, you should just use the leave hook.
    onBeforeLeave(el) {},

    // called when the leave transition starts.
    // use this to start the leaving animation.
    onLeave(el, done) {
      // call the done callback to indicate transition end
      // optional if used in combination with CSS
      done()
    },

    // called when the leave transition has finished and the
    // element has been removed from the DOM.
    onAfterLeave(el) {},

    // only available with v-show transitions
    onLeaveCancelled(el) {}
  }
}

這些鉤子可以與CSS過渡/動畫結合使用,或者單獨使用。

在使用僅JavaScript的過渡時,通常新增:css="false"屬性是個好主意。這會明確告訴Vue跳過自動CSS過渡檢測。除了效能略高外,這還可以防止CSS規則意外干擾過渡。

模板
<Transition
  ...
  :css="false"
>
  ...
</Transition>

使用:css="false"時,我們也完全負責控制過渡何時結束。在這種情況下,對於@enter@leave鉤子,需要提供done回撥。否則,鉤子將同步呼叫,並且過渡將立即完成。

以下是一個使用GSAP庫執行動畫的示例。當然,您可以使用任何其他動畫庫,例如Anime.jsMotion One

可重用過渡

可以透過Vue的元件系統重用過渡。要建立可重用的過渡,我們可以建立一個元件,該元件包裝了<Transition>元件,並將槽內容傳遞下去。

vue
<!-- MyTransition.vue -->
<script>
// JavaScript hooks logic...
</script>

<template>
  <!-- wrap the built-in Transition component -->
  <Transition
    name="my-transition"
    @enter="onEnter"
    @leave="onLeave">
    <slot></slot> <!-- pass down slot content -->
  </Transition>
</template>

<style>
/*
  Necessary CSS...
  Note: avoid using <style scoped> here since it
  does not apply to slot content.
*/
</style>

現在MyTransition可以像內建版本一樣匯入和使用。

模板
<MyTransition>
  <div v-if="show">Hello</div>
</MyTransition>

出現在過渡

如果您還希望在節點的初始渲染時應用過渡,可以新增appear屬性。

模板
<Transition appear>
  ...
</Transition>

元素間的過渡

除了使用v-if / v-show切換元素外,我們還可以使用v-if / v-else / v-else-if在兩個元素之間進行過渡,只要我們確保在任何給定時刻只顯示一個元素。

模板
<Transition>
  <button v-if="docState === 'saved'">Edit</button>
  <button v-else-if="docState === 'edited'">Save</button>
  <button v-else-if="docState === 'editing'">Cancel</button>
</Transition>
點選迴圈狀態

在Playground中嘗試

過渡模式

在上一個示例中,進入和離開元素同時進行動畫,我們必須將它們設定為position: absolute以避免在DOM中同時存在兩個元素時的佈局問題。

但是,在某些情況下這不是一個選項,或者根本不是我們想要的操作。我們可能希望先對離開元素進行動畫,然後只有在離開動畫完成後才將進入元素插入。手動編排這樣的動畫將非常複雜——幸運的是,我們可以透過傳遞一個mode屬性給<Transition>來啟用這種行為。

模板
<Transition mode="out-in">
  ...
</Transition>

以下是之前示例中帶有mode="out-in"的演示。

點選迴圈狀態

<Transition>還支援mode="in-out",儘管它使用得較少。

元件間的過渡

<Transition>也可以用於動態元件周圍。

模板
<Transition name="fade" mode="out-in">
  <component :is="activeComponent"></component>
</Transition>
元件A

動態過渡

<Transition>name等屬性也可以是動態的!這允許我們根據狀態變化動態應用不同的過渡。

模板
<Transition :name="transitionName">
  <!-- ... -->
</Transition>

當您使用Vue的過渡類約定定義了CSS過渡/動畫,並希望在這些過渡之間切換時,這可能會很有用。

您還可以根據元件的當前狀態,在JavaScript過渡鉤子中應用不同的行為。最後,建立動態過渡的終極方法是使用可複用過渡元件,這些元件接受props來改變要使用的過渡的性質。這可能聽起來有點陳詞濫調,但真正的限制實際上只是您的想象力。

具有鍵屬性(Key Attribute)的過渡

有時您需要強制重新渲染DOM元素,以便過渡發生。

以這個計數器元件為例

vue
<script setup>
import { ref } from 'vue';
const count = ref(0);

setInterval(() => count.value++, 1000);
</script>

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>
vue
<script>
export default {
  data() {
    return {
      count: 1,
      interval: null 
    }
  },
  mounted() {
    this.interval = setInterval(() => {
      this.count++;
    }, 1000)
  },
  beforeDestroy() {
    clearInterval(this.interval)
  }
}
</script>

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>

如果我們排除了key屬性,則只會更新文字節點,因此不會發生過渡。然而,當存在key屬性時,Vue知道每當count發生變化時,都會建立一個新的span元素,因此Transition元件有兩個不同的元素可以過渡。


相關

過渡已載入