本文 AI 產出,尚未審核

Vue3 – 樣式與 CSS 管理

主題:CSS Transition 與 Animation


簡介

在單頁應用程式中,使用者介面的流暢度直接影響使用者體驗。CSS TransitionCSS Animation 是前端開發者最常用的兩種動畫手段,配合 Vue3 的 <transition><transition-group> 元件,能以最小的程式碼量為元素增添自然的過渡與動態效果。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你掌握在 Vue3 中使用 CSS 動畫的技巧,讓你的專案不只功能完整,也能呈現「細膩」的 UI 交互。


核心概念

1. CSS Transition 基本原理

transition 讓屬性的變化在 起始值結束值 之間以指定的時間、緩動函式(easing)平滑過渡。最簡單的寫法如下:

/* 讓寬度在 0.3 秒內平滑變化 */
.box {
  transition: width 0.3s ease-out;
}

在 Vue 中,只要在元素的 class 切換或 v-showv-if 產生的顯隱變化,符合 CSS 設定的屬性,就會自動觸發 transition。

2. Vue3 的 <transition> 元件

Vue 為每個過渡階段自動套用特定的 class 名稱,預設規則如下(以 fade 為例):

階段 class name
進入前 (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 定義 transitionanimation,Vue 便會在元素掛載/卸載時自動播放。

範例 1:淡入淡出

<template>
  <button @click="show = !show">Toggle</button>

  <!-- 使用預設的 fade 過渡 -->
  <transition name="fade">
    <div v-if="show" class="box">Hello Vue3</div>
  </transition>
</template>

<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>

<style scoped>
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
.fade-enter-to,
.fade-leave-from {
  opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.4s ease;
}
.box {
  padding: 12px;
  background: #42b983;
  color: #fff;
  border-radius: 4px;
}
</style>

重點name="fade" 讓 Vue 自動產生 fade-xxxx 系列 class,開發者只需寫對應的樣式即可。

3. CSS Animation 與 @keyframes

animation 允許在多個關鍵幀(keyframe)之間自動播放,且可以設定迭代次數、方向、延遲等。與 transition 最大的差別是 不需要屬性變化的觸發點,只要把 animation 加到元素上,動畫就會自動執行。

範例 2:旋轉圖示

<template>
  <button @click="spinning = !spinning">
    <i :class="['icon', { spin: spinning }]">🔄</i>
    切換旋轉
  </button>
</template>

<script setup>
import { ref } from 'vue'
const spinning = ref(false)
</script>

<style scoped>
@keyframes rotate {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.spin {
  animation: rotate 1.2s linear infinite;
}
.icon {
  display: inline-block;
  font-size: 1.5rem;
}
</style>

在這個例子中,我們僅透過 class 切換 (spin) 來啟動或停止動畫,不需要 Vue 介入任何生命週期。

4. <transition-group>:列表動畫

當要對 多筆資料(如 v-for)做新增、刪除或排序動畫時,<transition-group> 能同時為每個項目套用過渡效果。它會自動為每個子元素產生 v-movev-enterv-leave 等 class。

範例 3:彈性列表

<template>
  <button @click="add">新增項目</button>
  <button @click="shuffle">隨機排序</button>

  <transition-group name="list" tag="ul" class="list">
    <li v-for="item in items" :key="item.id" class="list-item">
      {{ item.text }}
      <button @click="remove(item.id)">✕</button>
    </li>
  </transition-group>
</template>

<script setup>
import { ref } from 'vue'

let id = 0
const items = ref([])

function add() {
  items.value.push({ id: ++id, text: `項目 ${id}` })
}
function remove(id) {
  items.value = items.value.filter(i => i.id !== id)
}
function shuffle() {
  items.value = items.value
    .map(v => ({ v, sort: Math.random() }))
    .sort((a, b) => a.sort - b.sort)
    .map(({ v }) => v)
}
</script>

<style scoped>
.list {
  padding: 0;
  margin: 0;
}
.list-item {
  list-style: none;
  margin: 8px 0;
  padding: 8px 12px;
  background: #f5f5f5;
  border-radius: 4px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* 進入/離開動畫 */
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}
.list-enter-active,
.list-leave-active {
  transition: all 0.35s ease;
}

/* 移動動畫 (排序) */
.list-move {
  transition: transform 0.4s ease;
}
</style>

技巧<transition-group> 必須指定 tag(如 ul)與 key,否則 Vue 無法辨識哪一筆是「同一個」元素,動畫將失效。

5. 動態過渡名稱與 CSS 變數

在大型專案中,往往會有多種過渡樣式。透過 動態 nameCSS 變數,可以讓同一套 Vue 組件支援不同的動畫需求。

範例 4:切換不同過渡

<template>
  <select v-model="mode">
    <option value="fade">淡入淡出</option>
    <option value="slide">滑動</option>
  </select>

  <transition :name="mode">
    <div v-if="show" class="box">{{ mode }} 中</div>
  </transition>

  <button @click="show = !show">Toggle</button>
</template>

<script setup>
import { ref } from 'vue'
const mode = ref('fade')
const show = ref(true)
</script>

<style scoped>
/* fade */
.fade-enter-from,
.fade-leave-to { opacity: 0; }
.fade-enter-active,
.fade-leave-active { transition: opacity 0.3s ease; }

/* slide */
.slide-enter-from { transform: translateY(-20px); opacity: 0; }
.slide-enter-to   { transform: translateY(0);    opacity: 1; }
.slide-leave-from { transform: translateY(0);    opacity: 1; }
.slide-leave-to   { transform: translateY(20px); opacity: 0; }
.slide-enter-active,
.slide-leave-active { transition: all 0.35s ease; }

.box {
  margin-top: 12px;
  padding: 10px;
  background: #ff9f43;
  color: #fff;
  border-radius: 4px;
}
</style>

重點:name="mode" 讓過渡類型可由使用者或程式動態決定,提升元件的可重用性。

6. 結合 JavaScript Hook 控制動畫

Vue 仍提供 JavaScript 鉤子beforeEnterenterafterEnter…),當 CSS 無法滿足特殊需求時,可在這些鉤子裡使用 requestAnimationFramesetTimeout 等手動控制動畫。

範例 5:自訂高度過渡(從 0auto

<template>
  <button @click="open = !open">Toggle 高度</button>

  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
  >
    <div v-show="open" class="content">
      這是一段會自動伸縮的文字,內容可以很長很長...
    </div>
  </transition>
</template>

<script setup>
import { ref } from 'vue'
const open = ref(false)

function beforeEnter(el) {
  el.style.height = '0'
}
function enter(el, done) {
  const height = el.scrollHeight
  el.style.transition = 'height 0.35s ease'
  el.style.height = height + 'px'
  el.addEventListener('transitionend', done, { once: true })
}
function afterEnter(el) {
  el.style.height = 'auto'
}
function beforeLeave(el) {
  el.style.height = el.scrollHeight + 'px'
}
function leave(el, done) {
  // 觸發 reflow
  void el.offsetWidth
  el.style.transition = 'height 0.35s ease'
  el.style.height = '0'
  el.addEventListener('transitionend', done, { once: true })
}
function afterLeave(el) {
  el.style.height = ''
}
</script>

<style scoped>
.content {
  overflow: hidden;
  background: #e0f7fa;
  padding: 12px;
  margin-top: 8px;
  border-radius: 4px;
}
</style>

此範例展示了 「從 0 到 auto」 的過渡,純 CSS 難以實現,藉由 JavaScript 鉤子即可完成。


常見陷阱與最佳實踐

陷阱 說明 最佳做法
忘記 key <transition-group> 中若未提供唯一的 key,Vue 會把所有項目視為同一個,導致動畫失效或錯位。 為每個 v-for 項目設定穩定且唯一的 :key(如資料庫 ID)。
過度使用 !important transition/animation 受 CSS 優先權影響,!important 可能讓預期的過渡被覆寫。 盡量透過 scopedCSS Modules 管理樣式,避免使用 !important
同時使用 v-showv-if v-show 僅切換 display,不會觸發 enter/leavev-if 會重新掛載元素,兩者混用容易產生不一致的動畫。 根據需求選擇其一:需要保留狀態用 v-show,需要重新渲染用 v-if
動畫時間與實際變化不符 transition 的時間若寫得過長或過短,使用者會感到卡頓或突兀。 依照屬性變化的「感官」長度調整 duration,常見 200‑400ms 為最佳體驗。
未設定 transform 的硬體加速 大量動畫可能造成卡頓,尤其在手機上。 使用 transform: translate3d(0,0,0)will-change: transform, opacity 讓瀏覽器啟用 GPU 加速。
過度堆疊多層動畫 同時套用 transitionanimationJavaScript 鉤子,會產生衝突。 優先選擇一種方式,若必須混合,確保時間軸不重疊,且在 CSS 中使用 animation-fill-modetransition-delay 進行協調。

建議的開發流程

  1. 先寫 CSS:先在純 HTML/CSS 中驗證過渡或動畫是否正常。
  2. 加入 Vue <transition>:確認 class 名稱對應正確,並使用 scoped 防止全域污染。
  3. 測試不同裝置:使用 Chrome DevTools 的「Device Mode」或實機測試,觀察是否有掉幀。
  4. 若需高度自動:使用 JavaScript 鉤子或 @keyframes 搭配 max-height,避免 height: auto 無法被 transition 的限制。
  5. 最後加入可自訂的 props:將過渡名稱、時間、緩動函式抽成元件的 props,提升可重用性。

實際應用場景

場景 為何需要動畫 建議使用方式
表單驗證錯誤提示 錯誤訊息出現時若瞬間顯示,使用者容易忽略。 使用 <transition name="slide-down">,讓錯誤訊息從上方滑入並淡出。
彈出式對話框 (Modal) 背景遮罩與對話框同時淡入,提升聚焦感。 <transition name="fade"> 包裹遮罩,<transition name="scale"> 包裹對話框,兩者同時觸發。
清單排序/拖放 列表項目位置變動時若沒有動畫,會讓使用者感到突兀。 <transition-group name="list"> 搭配 list-move,自動產生平滑移動效果。
圖片輪播 圖片切換時需要淡入淡出或滑動過渡,提升視覺連貫性。 使用 transitionenter-active/leave-active,或 animationkeyframes 實作無縫切換。
側邊欄展開/收合 大量內容在側邊欄展開時需要同時調整寬度與透明度。 結合 widthopacitytransition,或使用 transform: translateX 搭配 will-change 提升效能。
Loading Skeleton 資料載入期間的骨架屏需要閃爍或脈衝效果。 使用 @keyframes pulseanimation,配合 v-if 控制顯示與隱藏。

總結

  • CSS Transition 讓屬性變化在固定時間內平滑過渡,適合單一屬性的簡單動畫。
  • CSS Animation 透過 @keyframes 可製作更複雜、持續或迭代的動畫,且不依賴狀態變化觸發。
  • Vue3 的 <transition><transition-group> 為動畫提供了 自動 class 注入生命週期鉤子動態名稱 等便利功能,讓開發者只需關注樣式本身。
  • 常見陷阱包括忘記 key、混用 v-show/v-if、以及忽略 GPU 加速。遵循「先寫 CSS、再加入 Vue」的流程,可減少除錯成本。
  • 在實務上,從表單驗證、彈窗、列表排序到 Loading Skeleton,皆能透過上述技巧提升使用者體驗與介面品質。

掌握了 CSS transitionanimation 的核心概念與 Vue3 的整合方式,你就能在專案中快速加入流暢、易維護的動畫效果,讓你的前端作品更具「活力」與「專業感**。」