Vue3 – Transition / 動畫效果
主題:動態 Transition Name
簡介
在單頁應用 (SPA) 中,使用 Vue3 內建的 <transition> 元件可以讓 UI 變化更自然、提升使用者體驗。大多數教學都會直接在 <transition> 上寫死 name="fade"、name="slide" 等固定的動畫名稱,然而在實際開發中,我們常常需要根據不同的情境或使用者操作 動態切換 這些動畫名稱,例如在同一個列表裡根據項目的類型切換不同的進出動畫,或在路由切換時依據前後頁面的層級選擇不同的過渡效果。
本篇文章將深入探討 動態 transition name 的寫法與原理,從基本概念到實作範例,並說明常見的陷阱與最佳實踐,幫助初學者到中階開發者在 Vue3 中靈活運用動畫,打造更具互動性的介面。
核心概念
1. <transition> 基礎回顧
<transition> 會在其子元素的 enter、leave、appear 階段自動添加 CSS class,配合 CSS transition 或 animation 完成動畫效果。預設的 class 命名規則如下(以 name="fade" 為例):
| 階段 | 加入的 class |
|---|---|
| enter-from | fade-enter-from |
| enter-active | fade-enter-active |
| enter-to | fade-enter-to |
| leave-from | fade-leave-from |
| leave-active | fade-leave-active |
| leave-to | fade-leave-to |
只要在 CSS 中為這些 class 定義對應的樣式,即可完成動畫。
2. 為什麼需要「動態」的 transition name?
- 多樣化 UI:同一個元件在不同情境下需要不同的過渡效果,例如彈出視窗的淡入淡出與側滑抽屜的滑入滑出。
- 條件切換:根據使用者操作或資料狀態改變動畫,例如「新增」使用
slide-down,「刪除」使用fade-out。 - 路由過渡:在 Vue Router 中,根據前後路由的層級或方向切換不同的過渡,提升導覽流暢度。
3. 動態設定 name 的方式
Vue3 允許將 <transition> 的 name 屬性綁定到一個 響應式變數,只要變數改變,過渡效果會自動套用新的 class 名稱。常見寫法如下:
<transition :name="currentTransition">
<div v-if="show">內容</div>
</transition>
在 setup() 中使用 ref 或 computed 來管理 currentTransition 即可。
4. 透過 v-bind 動態切換多個過渡
若需要同時控制 enter 與 leave 的不同動畫名稱,可以使用 enter-active-class、leave-active-class 等屬性,亦可綁定到變數:
<transition
:enter-active-class="enterClass"
:leave-active-class="leaveClass"
>
<div v-if="show">內容</div>
</transition>
這種寫法在「切換模式」的 UI(例如編輯模式 ↔ 預覽模式)特別有用。
5. 結合 Vue Router 的 meta 來決定過渡
在路由設定中可於 meta 加入自訂的過渡名稱,於 <router-view> 外層使用 :key 與 :name 動態取得:
// router/index.js
{
path: '/home',
component: Home,
meta: { transition: 'slide-left' }
},
{
path: '/about',
component: About,
meta: { transition: 'slide-right' }
}
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" :key="route.fullPath" />
</transition>
</router-view>
程式碼範例
下面提供 5 個實用範例,從最簡單的動態切換到結合 Router、組件間的協作,每段程式碼皆附上說明。
範例 1️⃣:最基本的動態 transition name
<!-- DynamicTransition.vue -->
<template>
<div>
<button @click="toggle">切換顯示</button>
<select v-model="currentTransition">
<option value="fade">淡入淡出</option>
<option value="slide-down">向下滑入</option>
<option value="zoom">放大縮小</option>
</select>
<transition :name="currentTransition">
<p v-if="show" class="box">Hello Vue3!</p>
</transition>
</div>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
const currentTransition = ref('fade')
function toggle() {
show.value = !show.value
}
</script>
<style scoped>
/* fade */
.fade-enter-from, .fade-leave-to { opacity: 0; }
.fade-enter-active, .fade-leave-active { transition: opacity .4s ease; }
/* slide-down */
.slide-down-enter-from { transform: translateY(-20px); opacity: 0; }
.slide-down-enter-to { transform: translateY(0); opacity: 1; }
.slide-down-leave-from { transform: translateY(0); opacity: 1; }
.slide-down-leave-to { transform: translateY(-20px); opacity: 0; }
.slide-down-enter-active, .slide-down-leave-active { transition: all .3s ease; }
/* zoom */
.zoom-enter-from, .zoom-leave-to { transform: scale(0.5); opacity: 0; }
.zoom-enter-to, .zoom-leave-from { transform: scale(1); opacity: 1; }
.zoom-enter-active, .zoom-leave-active { transition: all .35s cubic-bezier(.4,0,.2,1); }
</style>
說明:
currentTransition為ref,綁定在<transition>的:name。- 透過
<select>即可即時切換不同的動畫類型,適合在 設定頁 或 主題切換 時使用。
範例 2️⃣:依資料類型切換不同動畫
<!-- ListWithDynamicTransition.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<transition :name="getTransition(item.type)">
<span v-if="item.visible">{{ item.text }}</span>
</transition>
</li>
</ul>
<button @click="addItem('info')">新增 Info</button>
<button @click="addItem('warning')">新增 Warning</button>
</template>
<script setup>
import { reactive } from 'vue'
const items = reactive([
{ id: 1, text: '第一筆', type: 'info', visible: true },
{ id: 2, text: '第二筆', type: 'warning', visible: true }
])
let idCounter = 3
function addItem(type) {
items.push({ id: idCounter++, text: `新項目 (${type})`, type, visible: true })
}
/* 根據項目類型回傳不同的 transition name */
function getTransition(type) {
return type === 'warning' ? 'shake' : 'fade'
}
</script>
<style scoped>
/* fade */
.fade-enter-from, .fade-leave-to { opacity: 0; }
.fade-enter-active, .fade-leave-active { transition: opacity .3s; }
/* shake(警告) */
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
.shake-enter-from { opacity: 0; }
.shake-enter-active {
animation: shake .5s ease;
opacity: 1;
}
.shake-leave-active { opacity: 0; transition: opacity .2s; }
</style>
說明:
getTransition依item.type回傳不同的動畫名稱。- 這種寫法在 訊息列表、通知中心 等需要根據層級呈現不同過渡的場景非常實用。
範例 3️⃣:使用 enter-active-class / leave-active-class 分別控制進出動畫
<!-- SeparateEnterLeave.vue -->
<template>
<button @click="show = !show">Toggle Card</button>
<transition
:enter-active-class="enterClass"
:leave-active-class="leaveClass"
>
<div v-if="show" class="card">卡片內容</div>
</transition>
<div class="controls">
<label>Enter 動畫:
<select v-model="enterClass">
<option value="slide-in">Slide In</option>
<option value="fade-in">Fade In</option>
</select>
</label>
<label>Leave 動畫:
<select v-model="leaveClass">
<option value="slide-out">Slide Out</option>
<option value="fade-out">Fade Out</option>
</select>
</label>
</div>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
const enterClass = ref('slide-in')
const leaveClass = ref('slide-out')
</script>
<style scoped>
/* slide-in */
.slide-in-enter-from { transform: translateX(100%); }
.slide-in-enter-to { transform: translateX(0); }
.slide-in-enter-active { transition: transform .4s ease; }
/* slide-out */
.slide-out-leave-from { transform: translateX(0); }
.slide-out-leave-to { transform: translateX(100%); }
.slide-out-leave-active { transition: transform .3s ease; }
/* fade-in / fade-out */
.fade-in-enter-from, .fade-out-leave-to { opacity: 0; }
.fade-in-enter-to, .fade-out-leave-from { opacity: 1; }
.fade-in-enter-active, .fade-out-leave-active { transition: opacity .35s; }
.card {
width: 200px;
height: 120px;
background: #f5f5f5;
border: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
}
.controls { margin-top: 1rem; }
</style>
說明:
- 透過
enter-active-class與leave-active-class可在同一個元件中 分別設定 進入與離開的動畫。- 非常適合 表單切換、卡片翻轉 等需要不同進出效果的 UI。
範例 4️⃣:結合 Vue Router 的 meta.transition
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import Contact from '@/views/Contact.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { transition: 'slide-left' }
},
{
path: '/about',
name: 'About',
component: About,
meta: { transition: 'slide-right' }
},
{
path: '/contact',
name: 'Contact',
component: Contact,
meta: { transition: 'fade' }
}
]
export default createRouter({
history: createWebHistory(),
routes
})
<!-- App.vue -->
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/contact">Contact</router-link>
</nav>
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" :key="route.fullPath" />
</transition>
</router-view>
</template>
<script setup>
/* 無需額外程式碼,主要靠 router meta */
</script>
<style>
/* slide-left */
.slide-left-enter-from { transform: translateX(100%); }
.slide-left-enter-to { transform: translateX(0); }
.slide-left-enter-active,
.slide-left-leave-active { transition: transform .4s ease; }
.slide-left-leave-to { transform: translateX(-100%); }
/* slide-right */
.slide-right-enter-from { transform: translateX(-100%); }
.slide-right-enter-to { transform: translateX(0); }
.slide-right-enter-active,
.slide-right-leave-active { transition: transform .4s ease; }
.slide-right-leave-to { transform: translateX(100%); }
/* fade */
.fade-enter-from, .fade-leave-to { opacity: 0; }
.fade-enter-to, .fade-leave-from { opacity: 1; }
.fade-enter-active, .fade-leave-active { transition: opacity .3s; }
</style>
說明:
- 每條路由在
meta中定義自己的transition名稱。<router-view>使用 具名插槽 (v-slot) 取得當前路由資訊,動態套用對應動畫。- 這種寫法在 SPA 大型專案 中,讓不同頁面切換時保持一致且可維護的過渡規則。
範例 5️⃣:在同一個組件內切換「進入」與「離開」動畫的混合使用
<!-- ModalWithDynamicTransition.vue -->
<template>
<button @click="open = true">開啟 Modal</button>
<transition
:name="modalTransition"
@before-enter="setEnterDirection"
@before-leave="setLeaveDirection"
>
<div v-if="open" class="modal" @click.self="open = false">
<h3>動態過渡 Modal</h3>
<p>點擊背景關閉,或切換方向。</p>
<div class="controls">
<button @click="direction = 'left'">左側滑入</button>
<button @click="direction = 'right'">右側滑入</button>
<button @click="direction = 'top'">上方淡入</button>
</div>
</div>
</transition>
</template>
<script setup>
import { ref, computed } from 'vue'
const open = ref(false)
const direction = ref('left') // left / right / top
/* 根據 direction 動態產生 transition name */
const modalTransition = computed(() => {
switch (direction.value) {
case 'right': return 'slide-right'
case 'top': return 'fade-top'
default: return 'slide-left'
}
})
function setEnterDirection(el) {
// 可在此處根據 direction 加入自訂屬性,供 CSS 使用
el.dataset.dir = direction.value
}
function setLeaveDirection(el) {
// leave 時同樣保留方向資訊
el.dataset.dir = direction.value
}
</script>
<style scoped>
.modal {
position: fixed;
inset: 0;
background: rgba(0,0,0,.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal > div {
background: #fff;
padding: 2rem;
border-radius: 8px;
}
/* slide-left */
.slide-left-enter-from { transform: translateX(-100%); }
.slide-left-enter-to { transform: translateX(0); }
.slide-left-enter-active,
.slide-left-leave-active { transition: transform .35s ease; }
.slide-left-leave-to { transform: translateX(-100%); }
/* slide-right */
.slide-right-enter-from { transform: translateX(100%); }
.slide-right-enter-to { transform: translateX(0); }
.slide-right-enter-active,
.slide-right-leave-active { transition: transform .35s ease; }
.slide-right-leave-to { transform: translateX(100%); }
/* fade-top */
.fade-top-enter-from { opacity: 0; transform: translateY(-30px); }
.fade-top-enter-to { opacity: 1; transform: translateY(0); }
.fade-top-enter-active,
.fade-top-leave-active { transition: all .3s ease; }
.fade-top-leave-to { opacity: 0; transform: translateY(-30px); }
</style>
說明:
- 透過
computed動態產生modalTransition,讓使用者在 UI 上切換不同的彈出方向。@before-enter/@before-leave事件可在進入/離開前寫入自訂屬性,讓 CSS 取得額外資訊。- 此模式適合 彈窗、抽屜、提示訊息 等需要根據使用情境改變動畫的元件。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方案 / 最佳實踐 |
|---|---|---|
將 name 綁定到非響應式變數 |
動畫名稱不會更新,畫面仍使用舊的過渡 | 確保 name 來源是 ref、reactive 或 computed |
| 過渡類別與 CSS 重名 | 產生意外的樣式衝突,動畫失效 | 建議在專案中使用 命名空間(如 my-fade-enter)或 scoped CSS |
同時使用 name 與 enter-active-class |
兩者會相互覆寫,造成不預期的階段樣式 | 若要自訂進出階段,盡量只使用 enter-active-class / leave-active-class,或在 name 基礎上再加上自訂類別 |
在 <transition> 內放置多個根元素 |
Vue 只會對第一個子元素套用過渡,導致部分元素無動畫 | 使用 <template> 包裹 多個元素,或改用 <transition-group> |
| CSS 動畫與過渡同時使用 | 兩者的時間衝突,動畫卡頓 | 明確分離 transition 與 animation,或使用 animationend 事件確保結束後再切換狀態 |
路由過渡忘記加 key |
同一個組件切換路由時不會重新觸發過渡 | 在 <router-view> 中使用 :key="route.fullPath",保證每次切換都重新掛載元件 |
最佳實踐
- 保持過渡名稱語意化:如
slide-left、fade-up,讓其他開發者一眼就能懂。 - 將動畫 CSS 抽離成可重用的檔案(如
transitions.css),配合 BEM 或 CSS Modules,提升維護性。 - 使用
prefers-reduced-motion媒體查詢,對需要減少動畫的使用者提供無動畫或簡化版過渡。
@media (prefers-reduced-motion: reduce) {
.fade-enter-active,
.fade-leave-active,
.slide-left-enter-active,
.slide-left-leave-active { transition: none; }
}
實際應用場景
| 場景 | 為何需要動態 transition name | 典型實作方式 |
|---|---|---|
| 電商商品列表 | 不同類別商品(新品、特價、熱賣)需要不同的進場動畫,提升視覺層次感 | 依商品 type 回傳 fade、slide-up、bounce |
| 表單分頁 Wizard | 前後頁面切換方向不同(左滑/右滑),且在驗證失敗時需要「抖動」提示 | 使用 enter-active-class / leave-active-class 分別設定 slide-left、shake |
| 多語系切換 | 切換語系時希望使用淡入淡出或滑動過渡,根據使用者偏好切換 | 透過全局狀態 i18nTransition 控制 <transition :name="i18nTransition"> |
| 手機端抽屜選單 | 抽屜左滑出、右滑入,且在不同螢幕尺寸下可能改為淡入 | 在 resize 事件中更新 drawerTransition |
| SPA 大型平台 | 各子系統(Dashboard、Report、Setting)路由切換需不同過渡,提升導覽感知 | 在路由 meta.transition 中統一管理,配合 router-view 動態套用 |
總結
- 動態 transition name 為 Vue3 提供了彈性極高的動畫控制方式,讓開發者能根據 資料、使用者行為或路由資訊 變化即時切換過渡效果。
- 只要把
name、enter-active-class、leave-active-class綁定到 響應式變數,配合 computed 或 router meta,就能在不改寫大量程式碼的情況下,為不同情境提供專屬動畫。 - 在實作時要注意 變數的響應性、CSS 命名衝突、以及 過渡與動畫的時間協調,並遵循 可存取性(
prefers-reduced-motion)的最佳實踐。
掌握了這套「動態切換過渡」的技巧後,你的 Vue3 應用將能更靈活地呈現視覺效果,提升使用者體驗,同時保持程式碼的可維護性與可擴充性。祝開發順利,玩得開心!