Vue3 - Transition

tags: Vue
category: Front-End
description: Vue3 - Transition
created_at: 2022/07/17 15:00:00

cover image


回到 手把手開始寫 Vue3


前言

Vue 內建有提供兩個可以幫忙處理過渡(Transition),又或者是說漸變的組件。

分別是

  • Transition
  • TransitionGroup

這個東西在 React 沒有內建,所以會需要額外安裝套件來實作,例如 headlessui


官方基本範例

<script setup>
import { ref } from "vue";

const show = ref(true);
</script>

<template>
  <button @click="show = !show">Toggle</button>
  <Transition>
    <p v-if="show">hello</p>
  </Transition>
</template>

<style scoped>
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

可以看到當你點下 Toggle 這個按鈕,就會淡入淡出那個 p 標籤。

再來就是說明一下他是怎麼運作的,一樣上官方的圖

cover image

有以下這些 class,稍微簡單描述: (我是覺得也可以想像成類似生命週期的概念XD)

  • v-enter-from: 元素進來的初始狀態。
  • v-enter-active: 在進入的過程中。
  • v-enter-to: 元素進入的結束狀態。
  • v-leave-from: 元素離開的初始狀態。
  • v-leave-active: 在離開的過程中。
  • v-leave-to: 元素離開的最後狀態。

上範例:

<script setup>
import { ref } from "vue";

const show = ref(true);
</script>

<template>
  <button @click="show = !show">Toggle</button>
  <Transition>
    <p v-if="show">hello</p>
  </Transition>
</template>

<style scoped>
.v-enter-from {
  color: red;
}
.v-enter-active {
  transition: 3s;
}
.v-enter-to {
  color: blue;
}
.v-leave-from {
  color: green;
}
.v-leave-active {
  transition: 5s;
}
.v-leave-to {
  color: yellow;
}
</style>

這樣設定的話,那個 p 標籤會有這樣的行為:

  • 出現會以3秒的時間從紅色變為藍色
  • 消失會用5秒鐘,從綠色變回黃色
  • 當沒有要漸變時,就會是原來的黑色

也可以改 class 前綴,只要為 transition 加上 name

<script setup>
import { ref } from "vue";

const show = ref(true);
</script>

<template>
  <button @click="show = !show">Toggle</button>
  <Transition name="hi">
    <p v-if="show">hello</p>
  </Transition>
</template>

<style scoped>
.hi-enter-from {
  color: red;
}
.hi-enter-active {
  transition: 3s;
}
.hi-enter-to {
  color: blue;
}
.hi-leave-from {
  color: green;
}
.hi-leave-active {
  transition: 5s;
}
.hi-leave-to {
  color: yellow;
}
</style>

加上自訂動畫

<script setup>
import { ref } from "vue";

const show = ref(true);
</script>

<template>
  <button @click="show = !show">Toggle</button>
  <Transition>
    <p v-if="show" class="text-center">hello</p>
  </Transition>
</template>

<style scope>
.v-enter-active {
  animation: bounce-in 0.5s;
}
.v-leave-active {
  animation: bounce-in 0.5s reverse;
}

@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}
</style>

這樣子他在出現就會從 scale(0) 開始,而消失則會從 scale(1) 開始。

不過上面這一堆都是還要自己去額外寫對應 class 做操作,既然今天套上了 tailwindcss 應該會盡量少寫額外的 class,除非你有需要。

所以他其實還可以用別的方式操作,直接讓他在對應的時期幫你套上你要的 class

<script setup>
import { ref } from "vue";

const show = ref(true);
</script>

<template>
  <button @click="show = !show">Toggle</button>
  <Transition
    enter-from-class="text-red-500"
    enter-active-class="duration-[3s]"
    enter-to-class="text-blue-500"
    leave-from-class="text-green-500"
    leave-active-class="duration-[5s]"
    leave-to-class="text-yellow-500"
  >
    <p v-if="show">hello</p>
  </Transition>
</template>

最後他還有提供 hook 可以用,一樣貼官方範例來說明

<script setup>
import { ref } from "vue";

const show = ref(true);

// 進入初始狀態
const onBeforeEnter = (el) => {
  console.log("onBeforeEnter", el);
};

// 進入過程
const onEnter = (el, done) => {
  console.log("onEnter", (el, done));

  // 結束之後呼叫 done
  done();
};

// 在進入結束之後
const onAfterEnter = (el) => {
  console.log("onAfterEnter", el);
};

// 過程被打斷,只適用 v-show
const onEnterCancelled = (el) => {
  console.log("onEnterCancelled", el);
};

// 離開之前,也就是初始狀態
const onBeforeLeave = (el) => {
  console.log("onBeforeLeave", el);
};

// 離開過程中
const onLeave = (el, done) => {
  console.log("onLeave", (el, done));

  // 離開OK一樣記得呼叫 done
  done();
};

// 離開之後
const onAfterLeave = (el) => {
  console.log("onAfterLeave", el);
};

// 一樣也只適用於 v-show
const onLeaveCancelled = (el) => {
  console.log("onLeaveCancelled", el);
};
</script>

<template>
  <button @click="show = !show">Toggle</button>
  <Transition
    @before-enter="onBeforeEnter"
    @enter="onEnter"
    @after-enter="onAfterEnter"
    @enter-cancelled="onEnterCancelled"
    @before-leave="onBeforeLeave"
    @leave="onLeave"
    @after-leave="onAfterLeave"
    @leave-cancelled="onLeaveCancelled"
  >
    <p v-if="show">hello</p>
  </Transition>
</template>

上面這段可以稍微戳一戳玩一玩,然後有 hook 也還是可以跟原來的方法混用,像是下面這樣

  <Transition
    @before-enter="onBeforeEnter"
    @enter="onEnter"
    @after-enter="onAfterEnter"
    @enter-cancelled="onEnterCancelled"
    @before-leave="onBeforeLeave"
    @leave="onLeave"
    @after-leave="onAfterLeave"
    @leave-cancelled="onLeaveCancelled"
    enter-from-class="text-red-500"
    enter-active-class="duration-[3s]"
    enter-to-class="text-blue-500"
    leave-from-class="text-green-500"
    leave-active-class="duration-[5s]"
    leave-to-class="text-yellow-500"
  >

但是要注意的是,當 @enter 或是 @leave 呼叫 done() 之後就結束了,反之不呼叫就不會結束,這邊可以自己玩看看。

另外有時候你可能只想要 hook 不想要讓他幫你帶 class 避免其他錯誤,可以把它關掉

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

還有初始的進入狀態,可以加上 appear

<template>
  <button @click="show = !show">Toggle</button>
  <Transition
    appear
    enter-from-class="text-red-500"
    enter-active-class="duration-[3s]"
    enter-to-class="text-blue-500"
    leave-from-class="text-green-500"
    leave-active-class="duration-[5s]"
    leave-to-class="text-yellow-500"
  >
    <p v-if="show">hello</p>
  </Transition>
</template>

這樣的話畫面剛進去就會做 enter 相關的漸變。

最後再提一個,動畫的順序,在一般情況下是同時做

例如隱藏舊的並加入新的,一般情況是同時做,所以舊的會跑 leave,而新的會跑 enter,例如下面這段

<script setup>
import { ref } from "vue";

const count = ref(0);
</script>

<template>
  <button @click="count++">Add</button>
  <Transition
    enter-from-class="text-red-500"
    enter-active-class="duration-[3s]"
    enter-to-class="text-blue-500"
    leave-from-class="text-green-500"
    leave-active-class="duration-[5s]"
    leave-to-class="text-yellow-500"
  >
    <p v-if="count % 2 === 0">Count is even.</p>
    <p v-else>Count is odd.</p>
  </Transition>
</template>

如果要做到讓其中一邊先做,就要設定 mode

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

有兩個參數可以丟:

  • out-in: 先做 leave,後做 enter
  • in-out: 先做 enter,後做 leave

上面感覺交代得有點混,就放一個範例當作結尾吧,假設要做這樣的動畫

vue3 transition demo

寫起來大概會長得像這樣子

<script setup>
import { ref } from "vue";

const count = ref(0);
</script>

<template>
  <button @click="count++">Add</button>
  <Transition
    mode="out-in"
    enter-from-class="translate-y-8 opacity-0"
    enter-active-class="duration-200"
    enter-to-class="translate-y-0 opacity-100"
    leave-from-class="translate-y-0 opacity-100"
    leave-active-class="duration-200"
    leave-to-class="-translate-y-8 opacity-0"
  >
    <p v-if="count % 2 === 0">Count is even.</p>
    <p v-else>Count is odd.</p>
  </Transition>
</template>

寫起來的意思大概是,進入時從下往上,離開時從原位往更上面。透明度也跟著變動。


如果是有多個元素要做過渡,例如使用 v-for 產生的列表,需要使用 TransitionGroup,因為基本上跟 Transition 沒有差多少,篇幅也有點多了,就不特別寫了。




最後更新時間: 2022年07月17日.