本文 AI 產出,尚未審核

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-activelist-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);
    },
  },
};

技巧:在 enterleave 中使用 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-activeenter Hook,兩者會相互衝突。 選擇其一:純 CSS 或純 JS,或在 Hook 中手動控制 transition 屬性。
move 動畫失效 未為 .list-move 設定 transition,或元素的 displayinline 為搬移類別設定 transition,且確保元素為 blockflexgrid
過度渲染 大量資料一次性渲染會導致動畫卡頓。 使用 虛擬列表(如 vue-virtual-scroller)或分批載入。

最佳實踐

  1. 永遠使用 key:即使是臨時產生的索引(index)也要在資料不會變動時使用,否則會產生錯誤動畫。
  2. 分離樣式與邏輯:將過渡樣式寫在 .scss/.css,只在 JavaScript Hook 中處理「需要在動畫前/後執行的業務邏輯」。
  3. 使用 scoped 或 BEM 命名:避免全域樣式干擾其他元件的過渡類別。
  4. 設定 duration:若使用 transition,最好在 CSS 中明確寫出 duration,同時在 Vue 內部的 enter-active-classleave-active-class 中保持一致。
  5. 測試不同裝置:手機與桌面渲染效能差異大,適度降低動畫時長或改用 opacitytransform(GPU 加速)可提升流暢度。

實際應用場景

場景 為何使用 transition-group
待辦清單 (Todo List) 新增、完成、刪除項目時需要即時的淡入淡出與搬移效果,提升使用者操作的回饋感。
聊天訊息列表 訊息進入時從底部滑入,舊訊息被移除或載入更多時使用平滑過渡,避免畫面跳動。
商品卡片網格 篩選或排序商品時,卡片搬移、淡出/淡入,使使用者清楚感知變化。
照片牆 (Masonry Layout) 圖片載入或刪除後,其他圖片自動重新排列並伴隨過渡,提升視覺美感。
動態表格 表格列的增刪、排序或分頁切換,都可使用 transition-group 讓資料變化更自然。

總結

<transition-group> 是 Vue3 為 多元素過渡 所提供的強大工具,透過簡潔的語法與 CSS/JS 的彈性結合,我們可以在列表、網格或任何需要同時處理多筆資料的 UI 中,輕鬆加入 流暢且易於維護 的動畫效果。掌握以下要點,即可在實務開發中發揮最佳效能:

  1. 必須提供唯一 key,讓 Vue 正確追蹤元素狀態。
  2. 選擇 CSS 或 JavaScript Hook 作為過渡方式,避免混用造成衝突。
  3. 適當使用 move,讓排序或重新排列時自動產生搬移動畫。
  4. 留意效能:大量資料時考慮虛擬化或分批渲染。
  5. 遵守語意化標籤(如 uldiv)與 CSS 命名規範,保持程式碼可讀且不互相干擾。

只要遵循上述原則,你就能在 Vue3 專案中,為動態列表打造出 自然、易懂且具備專業感 的動畫體驗,提升使用者的互動滿意度與產品品質。祝開發順利,玩得開心! 🎉