Vue3 – Transition / 動畫效果
主題:transition-group
簡介
在單頁應用程式中,列表資料的增刪、排序或過濾常常會讓畫面瞬間改變,若沒有任何過渡效果,使用者會感到突兀,甚至難以辨識哪些項目是被加入或移除的。Vue3 提供的 <transition-group> 正是為了在 多元素列表 中加入動畫、過渡的利器,它不僅支援 CSS 動畫,還可以結合 JavaScript Hook 進行更彈性的控制。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整掌握 transition-group 的用法,讓你的 Vue3 應用在動態列表上也能呈現流暢且具備視覺回饋的效果。
核心概念
1. 為什麼需要 <transition-group>?
- 單一元素:
<transition>僅能包住一個根元素,用於單個元件的進出動畫。 - 多元素列表:當有 多筆資料 需要同時進行動畫(例如
v-for渲染的<li>),必須使用<transition-group>,它會為每一個子元素自動加入過渡類別。
重點:
<transition-group>只負責 子元素 的過渡,父層本身不會產生動畫。
2. 基本語法
<transition-group name="list" tag="ul">
<li v-for="item in items" :key="item.id" class="list-item">
{{ item.text }}
</li>
</transition-group>
name→ 動畫類別前綴(預設會產生list-enter-active、list-leave-active等)。tag→ 包裹子元素的 HTML 標籤,預設是<span>,常用<ul>、<div>等。key→ 必須為唯一值,Vue 依此判斷哪些元素是「新增」或「移除」。
3. CSS 版過渡
3.1 進入與離開的基本樣式
/* 進入動畫 */
.list-enter-from { opacity: 0; transform: translateY(-20px); }
.list-enter-active { transition: all 0.3s ease; }
.list-enter-to { opacity: 1; transform: translateY(0); }
/* 離開動畫 */
.list-leave-from { opacity: 1; transform: translateY(0); }
.list-leave-active { transition: all 0.3s ease; }
.list-leave-to { opacity: 0; transform: translateY(20px); }
說明:Vue 會在元素 插入 前套用
-enter-from,接著立即切換到-enter-to,同時在-enter-active裡執行過渡。離開時則相反。
3.2 使用 move 讓排序平滑過渡
當資料列表被重新排序時,<transition-group> 會自動為「搬移」的元素加上 -move 類別,只要在 CSS 中設定 transition 即可。
.list-move {
transition: transform 0.4s ease;
}
4. JavaScript Hook:自訂動畫
有時候單純的 CSS 難以滿足需求(例如需要在動畫結束後執行 Ajax),Vue 允許在 v-on 綁定下的 Hook 取得更細緻的控制。
<transition-group
name="fade"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@leave="leave"
@after-leave="afterLeave"
>
<div v-for="msg in messages" :key="msg.id" class="msg">
{{ msg.text }}
</div>
</transition-group>
export default {
methods: {
beforeEnter(el) {
el.style.opacity = 0;
el.style.transform = 'translateY(-10px)';
},
enter(el, done) {
const delay = el.dataset.index * 50; // 依序淡入
setTimeout(() => {
el.style.transition = 'all 0.4s ease';
el.style.opacity = 1;
el.style.transform = 'translateY(0)';
done(); // 必須呼叫 done 才算結束
}, delay);
},
afterEnter(el) {
// 動畫結束後可執行清理或資料統計
console.log('enter finished', el);
},
leave(el, done) {
el.style.transition = 'all 0.3s ease';
el.style.opacity = 0;
el.style.transform = 'translateY(20px)';
setTimeout(done, 300);
},
afterLeave(el) {
console.log('leave finished', el);
},
},
};
技巧:在
enter、leave中使用done回呼,才能正確告訴 Vue 動畫已完成,避免出現卡住的情況。
5. 進階範例:結合 v-move 與過濾
以下示範一個「即時搜尋」的列表,使用 transition-group 讓新增、刪除與排序都帶有動畫。
<template>
<div class="demo">
<input v-model="keyword" placeholder="搜尋關鍵字" />
<transition-group name="list" tag="ul" class="list">
<li
v-for="item in filteredItems"
:key="item.id"
class="list-item"
>
{{ item.name }}
</li>
</transition-group>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const items = ref([
{ id: 1, name: 'Vue.js' },
{ id: 2, name: 'React' },
{ id: 3, name: 'Angular' },
{ id: 4, name: 'Svelte' },
]);
const keyword = ref('');
const filteredItems = computed(() => {
if (!keyword.value) return items.value;
return items.value.filter(i =>
i.name.toLowerCase().includes(keyword.value.toLowerCase())
);
});
</script>
<style scoped>
.list {
list-style: none;
padding: 0;
}
.list-item {
margin: 6px 0;
padding: 8px 12px;
background: #f5f5f5;
border-radius: 4px;
}
/* transition-group CSS */
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateY(10px);
}
.list-enter-active,
.list-leave-active {
transition: all 0.35s ease;
}
.list-move {
transition: transform 0.4s ease;
}
</style>
- 關鍵點:
filteredItems為計算屬性,當keyword改變時,Vue 會重新渲染列表,transition-group自動為 新增、移除、搬移 的<li>加上對應類別,產生流暢的過渡效果。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
缺少唯一 key |
Vue 無法辨識哪些元素是新增或刪除,會導致動畫不正確或錯位。 | 為每個子元素提供 唯一且穩定 的 key(建議使用資料庫 ID)。 |
使用 tag="span" 包裹 <li> |
<span> 不是合法的 <ul> 子元素,會破壞 HTML 結構。 |
明確指定 tag="ul"(或其他符合語意的標籤)。 |
| CSS 動畫與 JS Hook 同時使用 | 若同時設定 -enter-active 與 enter Hook,兩者會相互衝突。 |
選擇其一:純 CSS 或純 JS,或在 Hook 中手動控制 transition 屬性。 |
move 動畫失效 |
未為 .list-move 設定 transition,或元素的 display 為 inline。 |
為搬移類別設定 transition,且確保元素為 block、flex 或 grid。 |
| 過度渲染 | 大量資料一次性渲染會導致動畫卡頓。 | 使用 虛擬列表(如 vue-virtual-scroller)或分批載入。 |
最佳實踐
- 永遠使用
key:即使是臨時產生的索引(index)也要在資料不會變動時使用,否則會產生錯誤動畫。 - 分離樣式與邏輯:將過渡樣式寫在
.scss/.css,只在 JavaScript Hook 中處理「需要在動畫前/後執行的業務邏輯」。 - 使用
scoped或 BEM 命名:避免全域樣式干擾其他元件的過渡類別。 - 設定
duration:若使用transition,最好在 CSS 中明確寫出duration,同時在 Vue 內部的enter-active-class、leave-active-class中保持一致。 - 測試不同裝置:手機與桌面渲染效能差異大,適度降低動畫時長或改用
opacity、transform(GPU 加速)可提升流暢度。
實際應用場景
| 場景 | 為何使用 transition-group |
|---|---|
| 待辦清單 (Todo List) | 新增、完成、刪除項目時需要即時的淡入淡出與搬移效果,提升使用者操作的回饋感。 |
| 聊天訊息列表 | 訊息進入時從底部滑入,舊訊息被移除或載入更多時使用平滑過渡,避免畫面跳動。 |
| 商品卡片網格 | 篩選或排序商品時,卡片搬移、淡出/淡入,使使用者清楚感知變化。 |
| 照片牆 (Masonry Layout) | 圖片載入或刪除後,其他圖片自動重新排列並伴隨過渡,提升視覺美感。 |
| 動態表格 | 表格列的增刪、排序或分頁切換,都可使用 transition-group 讓資料變化更自然。 |
總結
<transition-group> 是 Vue3 為 多元素過渡 所提供的強大工具,透過簡潔的語法與 CSS/JS 的彈性結合,我們可以在列表、網格或任何需要同時處理多筆資料的 UI 中,輕鬆加入 流暢且易於維護 的動畫效果。掌握以下要點,即可在實務開發中發揮最佳效能:
- 必須提供唯一
key,讓 Vue 正確追蹤元素狀態。 - 選擇 CSS 或 JavaScript Hook 作為過渡方式,避免混用造成衝突。
- 適當使用
move,讓排序或重新排列時自動產生搬移動畫。 - 留意效能:大量資料時考慮虛擬化或分批渲染。
- 遵守語意化標籤(如
ul、div)與 CSS 命名規範,保持程式碼可讀且不互相干擾。
只要遵循上述原則,你就能在 Vue3 專案中,為動態列表打造出 自然、易懂且具備專業感 的動畫體驗,提升使用者的互動滿意度與產品品質。祝開發順利,玩得開心! 🎉