Vue3 – Transition 元件基本用法
簡介
在單頁應用 (SPA) 中,畫面切換與資料變化往往是頻繁且即時的。若沒有適當的過渡效果,使用者會感受到突兀的「跳躍」感,降低介面的可用性與美觀度。Vue 3 提供的 <transition> 元件,讓開發者只需要少量的宣告,就能為元素或組件加入 CSS 或 JavaScript 動畫,且支援進出、進入、離開等多種狀態。掌握它的基本用法,不僅能提升使用者體驗,還能在開發過程中保持 可讀性 與 可維護性。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,最後帶入實務應用場景,幫助從初學者到中級開發者快速上手 Vue 3 的 <transition>。
核心概念
1. <transition> 的工作原理
<transition> 會在其包裹的元素或子組件 進入 (enter)、離開 (leave) 時,自動為目標元素加入、移除 CSS class。這些 class 依序為:
| 階段 | 加入的 class | 移除的 class |
|---|---|---|
| 進入開始 (enter-from) | v-enter-from |
v-enter-active |
| 進入過程 (enter-active) | v-enter-active |
- |
| 進入結束 (enter-to) | v-enter-to |
v-enter-active |
| 離開開始 (leave-from) | v-leave-from |
v-leave-active |
| 離開過程 (leave-active) | v-leave-active |
- |
| 離開結束 (leave-to) | v-leave-to |
v-leave-active |
預設的 class 前綴是 v-,可以透過 name 屬性自訂,例如 name="fade" 後,產生的 class 會是 fade-enter-from、fade-leave-to 等。
重點:只要在 CSS 中為這些 class 定義
transition或animation,Vue 便會在正確的時機觸發動畫。
2. 基本語法
<template>
<button @click="show = !show">Toggle</button>
<transition name="fade">
<p v-if="show">這段文字會淡入淡出</p>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>
上述範例中,<p> 元素在 show 為 true 時插入 DOM,為 false 時移除。<transition> 會根據 fade 名稱自動套用對應的 CSS。
3. CSS 範例
/* fade 進入 */
.fade-enter-from { opacity: 0; }
.fade-enter-active { transition: opacity 0.4s ease; }
.fade-enter-to { opacity: 1; }
/* fade 離開 */
.fade-leave-from { opacity: 1; }
.fade-leave-active { transition: opacity 0.4s ease; }
.fade-leave-to { opacity: 0; }
只要寫好這六個 class,Vue 就會在元素掛載與卸載的過程中完成淡入淡出。
程式碼範例
以下提供 5 個常見且實用的範例,每個範例都會說明要點與可自行調整的參數。
範例 1️⃣ 簡易淡入淡出(上文已示)
<template>
<button @click="visible = !visible">Toggle Box</button>
<transition name="fade">
<div v-if="visible" class="box">Box</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const visible = ref(false)
</script>
<style scoped>
.box {
width: 120px;
height: 120px;
background: #42b983;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
/* fade 動畫 */
.fade-enter-from { opacity: 0; transform: scale(0.8); }
.fade-enter-active { transition: opacity .3s, transform .3s; }
.fade-enter-to { opacity: 1; transform: scale(1); }
.fade-leave-from { opacity: 1; transform: scale(1); }
.fade-leave-active { transition: opacity .3s, transform .3s; }
.fade-leave-to { opacity: 0; transform: scale(0.8); }
</style>
說明:同時使用
opacity與transform,讓盒子在淡入時有縮放的感覺,提升視覺層次。
範例 2️⃣ 列表項目逐一淡入(使用 appear)
<template>
<transition-group name="list" tag="ul" appear>
<li v-for="item in items" :key="item.id" class="item">
{{ item.text }}
</li>
</transition-group>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, text: 'Vue' },
{ id: 2, text: 'React' },
{ id: 3, text: 'Angular' }
])
</script>
<style scoped>
.list-enter-from { opacity: 0; transform: translateY(20px); }
.list-enter-active { transition: all .4s ease; }
.list-enter-to { opacity: 1; transform: translateY(0); }
.list-leave-from { opacity: 1; transform: translateY(0); }
.list-leave-active { transition: all .4s ease; }
.list-leave-to { opacity: 0; transform: translateY(-20px); }
</style>
要點:
- 使用
<transition-group>處理 多元素 的過渡。appear讓組件在首次渲染時也套用進入動畫。- 必須為每個子元素提供唯一的
key,否則 Vue 無法正確追蹤。
範例 3️⃣ 自訂過渡名稱與持續時間
<template>
<button @click="show = !show">切換</button>
<transition name="slide" mode="out-in" :duration="500">
<div v-if="show" class="panel">滑動面板</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>
<style scoped>
.slide-enter-from { transform: translateX(-100%); }
.slide-enter-active { transition: transform 0.5s ease; }
.slide-enter-to { transform: translateX(0); }
.slide-leave-from { transform: translateX(0); }
.slide-leave-active { transition: transform 0.5s ease; }
.slide-leave-to { transform: translateX(100%); }
</style>
說明:
mode="out-in"確保舊的元素離開完畢後才插入新的元素,常用於 頁面切換。duration可直接在<transition>上設定,以免在 CSS 中硬編碼時間。
範例 4️⃣ 使用 JavaScript Hook 控制動畫
<template>
<button @click="show = !show">Toggle</button>
<transition
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<div v-if="show" class="js-box">JS 動畫盒子</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
function beforeEnter(el) {
el.style.opacity = 0
el.style.transform = 'scale(0.5)'
}
function enter(el, done) {
// 使用 requestAnimationFrame 確保樣式變更被瀏覽器捕捉
requestAnimationFrame(() => {
el.style.transition = 'all 0.4s ease'
el.style.opacity = 1
el.style.transform = 'scale(1)'
// 動畫結束時呼叫 done
el.addEventListener('transitionend', done, { once: true })
})
}
function leave(el, done) {
el.style.transition = 'all 0.4s ease'
el.style.opacity = 0
el.style.transform = 'scale(0.5)'
el.addEventListener('transitionend', done, { once: true })
}
</script>
<style scoped>
.js-box {
width: 150px;
height: 150px;
background: #ff6f61;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
</style>
重點:
- 當 CSS 無法滿足需求(例如根據資料動態計算高度)時,可改用 JavaScript Hook。
done必須在動畫結束時呼叫,否則 Vue 會認為過渡仍在進行,導致元素卡住。
範例 5️⃣ 動態切換過渡類型(利用 :name)
<template>
<div class="controls">
<label>過渡類型:
<select v-model="type">
<option value="fade">淡入淡出</option>
<option value="zoom">縮放</option>
<option value="slide">滑動</option>
</select>
</label>
<button @click="show = !show">Toggle</button>
</div>
<transition :name="type">
<div v-if="show" class="box">{{ type }} 效果</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
const type = ref('fade')
</script>
<style scoped>
.box {
width: 200px;
height: 80px;
background: #5c6bc0;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
/* fade */
.fade-enter-from, .fade-leave-to { opacity: 0; }
.fade-enter-active, .fade-leave-active { transition: opacity .3s; }
/* zoom */
.zoom-enter-from, .zoom-leave-to { transform: scale(0.5); opacity: 0; }
.zoom-enter-active, .zoom-leave-active { transition: transform .3s, opacity .3s; }
.zoom-enter-to, .zoom-leave-from { transform: scale(1); opacity: 1; }
/* slide */
.slide-enter-from { transform: translateX(-100%); }
.slide-enter-active { transition: transform .4s ease; }
.slide-enter-to { transform: translateX(0); }
.slide-leave-from { transform: translateX(0); }
.slide-leave-active { transition: transform .4s ease; }
.slide-leave-to { transform: translateX(100%); }
</style>
說明:透過
:name綁定變數,使用者可以即時切換不同的過渡效果,適合 設定頁面 或 主題切換 的情境。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方式 / 最佳實踐 |
|---|---|---|
忘記設定唯一 key(在 <transition-group>) |
列表變化時動畫失效或出現錯位 | 為每個子項目提供穩定且唯一的 key(如資料庫 ID) |
過渡時間與 CSS transition 時間不一致 |
動畫卡住、enter/leave 事件不觸發 done |
使用 <transition> 的 duration 屬性或在 CSS 中明確寫出時間,保持一致 |
在 v-if 與 v-show 同時使用 |
v-show 直接改變 display,會讓 <transition> 無法捕捉離開階段 |
僅使用 v-if(真正的掛載/卸載)或在需要快速切換時改為 v-show 並自行處理過渡 |
| 在過渡期間改變元素尺寸 | 會導致瀏覽器重新計算布局,動畫卡頓 | 若需要尺寸變化,建議改用 transition 搭配 max-height 或使用 JavaScript Hook 手動控制 |
過渡層級過深(多層 <transition>) |
產生不必要的重繪,效能下降 | 只在必要的外層套用 <transition>,內層若不需要可直接使用 CSS 動畫 |
最佳實踐
- 盡量使用 CSS:除非有特別需求,CSS 動畫的效能遠高於 JavaScript。
- 命名規則統一:使用
name屬性或自訂前綴,保持樣式檔案可讀性。 - 分離動畫與布局:避免在過渡期間同時改變
display、position等會觸發重排的屬性。 - 使用
mode="out-in":在切換路由或卡片時,確保舊內容先離開再進入,避免閃爍。 - 測試不同裝置:手機瀏覽器的硬體加速與桌面不同,適當調整
transition-timing-function(如cubic-bezier)提升感受。
實際應用場景
| 場景 | 需求 | 建議的 <transition> 用法 |
|---|---|---|
| 表單驗證錯誤提示 | 錯誤訊息出現時淡入、消失時淡出 | 包裹錯誤訊息 <transition name="fade">,搭配 v-if 顯示/隱藏 |
| 側邊選單 (Drawer) | 點擊按鈕時滑入,關閉時滑出 | 使用 name="slide"、mode="out-in",可加上 .slide-enter-active 設定 transform |
| 圖片輪播 | 圖片切換時使用淡入淡出或縮放 | <transition-group> 搭配 key 為圖片 URL,使用 appear 讓首張圖也有過渡 |
| 路由切換 (Vue Router) | 每次切換頁面時有統一的過渡效果 | 在 App.vue 中包裹 <router-view>:<transition name="route-fade" mode="out-in"> |
| 動態表格資料更新 | 新增列時滑入、刪除列時滑出 | <transition-group tag="tbody">,為每列 <tr> 設定唯一 key,定義 enter/leave 動畫 |
透過上述範例,可見 <transition> 在日常開發中幾乎無所不在,僅需少量設定即可大幅提升介面的 流暢度 與 專業感。
總結
Vue 3 的 <transition> 元件提供了 聲明式、彈性 的動畫機制,讓開發者能在 進入、離開、切換 等多種情境下,快速套用 CSS 或 JavaScript 動畫。掌握以下要點,即可在專案中安全且有效地使用:
- 了解 六個自動產生的 class(enter/leave 系列),並以
name或自訂前綴管理樣式。 - 使用
<transition-group>處理多元素過渡,務必提供唯一key。 - 依需求選擇 CSS 或 JavaScript Hook,CSS 為首選,JS 為例外。
- 注意 duration、
mode、appear等屬性,避免動畫卡頓或錯誤。 - 在實務上,從表單驗證、抽屜、輪播、路由切換到動態表格,都能透過
<transition>讓 UI 更具「活力」與「可用性」。
只要遵循 最佳實踐,並根據實際需求微調過渡時間與曲線,Vue 3 的過渡效果將成為你開發現代 Web 應用時不可或缺的利器。祝你在專案中玩出各式炫麗動畫,為使用者帶來更佳的體驗!