本文 AI 產出,尚未審核

Vue3

單元:Transition / 動畫效果

主題:CSS Variables 動畫


簡介

在單頁應用程式中,動畫不只是視覺上的點綴,更是提升使用者體驗、引導操作流程的重要手段。Vue3 提供了 <Transition><TransitionGroup> 兩個內建組件,讓開發者可以輕鬆為元素切換、列表變化加入過渡效果。

近年來,CSS Variables(自訂屬性) 成為管理樣式的利器。結合 Vue 的響應式資料與 CSS 變數,我們可以在不撰寫繁雜的 JavaScript 動畫程式碼的前提下,實現高度可配置、可重用的動畫效果。本文將從概念說明、實作範例、常見陷阱與最佳實踐,完整介紹在 Vue3 中如何運用 CSS Variables 來打造流暢且可維護的動畫。


核心概念

1. CSS Variables 的基本原理

CSS Variables(自訂屬性)以 -- 開頭,並可在任意層級宣告、繼承。透過 var(--name, fallback) 取得變數值,且變數本身可以在 run‑time 被改變,讓樣式即時更新。

/* 全域變數 */
:root {
  --primary-color: #42b983;
  --anim-duration: 300ms;
}

/* 使用變數 */
.button {
  background-color: var(--primary-color);
  transition: background-color var(--anim-duration) ease;
}

重點:變數的變更會觸發瀏覽器重新計算樣式,等同於改變了對應的 CSS 屬性,從而觸發 CSS transition 或 animation。

2. 為什麼在 Vue3 中結合 CSS Variables?

Vue3 CSS Variables
響應式:資料變動自動驅動 UI 更新 即時樣式:變數變更即觸發 CSS 動畫
組件化:可在 <script setup> 中直接操作 全域/局部:可在根元素或單一組件內定義
生命週期鉤子onBeforeEnteronEnter 過渡:配合 transitionanimation 使用

將 Vue 的資料狀態映射到 CSS 變數,便能在 純 CSS 中完成大部分動畫邏輯,減少 JavaScript 負擔、提升效能。

3. 在 Vue3 中使用 <Transition> 搭配 CSS Variables

<Transition> 會在元素進入或離開時自動添加以下 class:

階段 產生的 class
進入前 (before-enter) v-enter-from
進入中 (enter) v-enter-active
進入後 (enter-to) v-enter-to
離開前 (before-leave) v-leave-from
離開中 (leave) v-leave-active
離開後 (leave-to) v-leave-to

只要在這些 class 中使用 CSS 變數,即可控制動畫的 起始值、結束值、時長 等參數。


程式碼範例

以下示範 5 個實用範例,從最簡單的淡入淡出到結合 JavaScript 生命週期動態調整變數。

範例 1:淡入淡出(使用 opacity 變數)

<template>
  <button @click="show = !show">Toggle Box</button>
  <Transition name="fade">
    <div v-if="show" class="box"></div>
  </Transition>
</template>

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

<style scoped>
/* 定義動畫時長變數 */
.box {
  width: 120px;
  height: 120px;
  background: #42b983;
  --fade-duration: 400ms;
}

/* 進入/離開的 CSS */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity var(--fade-duration) ease;
}
.fade-enter-to,
.fade-leave-from {
  opacity: 1;
}
</style>

說明--fade-duration.box 中宣告,讓同一個組件的多個過渡可共享同一個時長設定。

範例 2:縮放動畫(使用 transform 變數)

<template>
  <button @click="open = !open">Toggle Card</button>
  <Transition name="scale">
    <div v-if="open" class="card">Vue3 Card</div>
  </Transition>
</template>

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

<style scoped>
.card {
  width: 200px;
  height: 120px;
  background: #ffdd57;
  color: #333;
  display: flex;
  align-items: center;
  justify-content: center;
  --scale-start: 0.8;
  --scale-end: 1;
  --scale-duration: 250ms;
}

/* 起始與結束的 transform 由變數決定 */
.scale-enter-from,
.scale-leave-to {
  transform: scale(var(--scale-start));
}
.scale-enter-to,
.scale-leave-from {
  transform: scale(var(--scale-end));
}
.scale-enter-active,
.scale-leave-active {
  transition: transform var(--scale-duration) cubic-bezier(0.4, 0, 0.2, 1);
}
</style>

提示:若想在不同裝置上調整動畫大小,只需要修改 --scale-start--scale-end 即可,無需改動 CSS 內容。

範例 3:從 Vue 資料動態控制變數(顏色漸變)

<template>
  <button @click="toggle">Switch Theme</button>
  <Transition name="color-fade">
    <div v-if="isDark" class="theme-box"></div>
  </Transition>
</template>

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

const isDark = ref(false)

// 監聽變更,動態更新 CSS 變數
watch(isDark, (val) => {
  const root = document.documentElement
  root.style.setProperty('--bg-start', val ? '#222' : '#fff')
  root.style.setProperty('--bg-end', val ? '#fff' : '#222')
})
</script>

<style scoped>
.theme-box {
  width: 100%;
  height: 150px;
  /* 初始變數值會在 watch 中被設定 */
  background: var(--bg-start);
  --color-duration: 600ms;
}

/* 使用變數做顏色過渡 */
.color-fade-enter-from,
.color-fade-leave-to {
  background: var(--bg-start);
}
.color-fade-enter-to,
.color-fade-leave-from {
  background: var(--bg-end);
}
.color-fade-enter-active,
.color-fade-leave-active {
  transition: background var(--color-duration) ease-in-out;
}
</style>

關鍵:透過 watch 監聽 Vue 狀態,直接在 JavaScript 端修改 :root 上的 CSS 變數,讓所有使用該變數的樣式同步變更。

範例 4:使用 @keyframes 搭配 CSS 變數(持續旋轉)

<template>
  <button @click="spinning = !spinning">Toggle Spin</button>
  <Transition name="spin">
    <div v-if="spinning" class="spinner"></div>
  </Transition>
</template>

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

<style scoped>
.spinner {
  width: 80px;
  height: 80px;
  border: 8px solid #e0e0e0;
  border-top-color: #42b983;
  border-radius: 50%;
  --spin-duration: 1.5s;
}

/* 定義 keyframes,使用變數作為動畫時長 */
@keyframes rotate {
  to {
    transform: rotate(360deg);
  }
}

/* 只在進入時套用動畫 */
.spin-enter-active {
  animation: rotate var(--spin-duration) linear infinite;
}
.spin-leave-active {
  animation: none;
}
</style>

說明animationduration 直接由 --spin-duration 控制,若想調整速度,只要改變變數值即可,無需重新寫 @keyframes

範例 5:結合 Vue 生命週期鉤子自訂變數(彈性控制)

<template>
  <Transition
    @before-enter="setEnterVars"
    @enter="setEnterActive"
    @leave="setLeaveVars"
  >
    <div v-if="show" class="panel">彈出面板</div>
  </Transition>

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

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

function setEnterVars(el) {
  // 進入前設定起始高度
  el.style.setProperty('--panel-height-start', '0px')
  el.style.setProperty('--panel-height-end', '200px')
}
function setEnterActive(el) {
  // 觸發 transition
  el.style.height = 'var(--panel-height-end)'
}
function setLeaveVars(el) {
  // 離開時改成收縮高度
  el.style.height = 'var(--panel-height-start)'
}
</script>

<style scoped>
.panel {
  overflow: hidden;
  background: #e3f2fd;
  transition: height 300ms ease;
  /* 初始高度由 JS 設定 */
  height: var(--panel-height-start);
}
</style>

技巧:利用 <Transition> 的 JavaScript 鉤子直接在元素上設定 CSS 變數,讓動畫參數可以依照當前狀態或外部資料動態調整,彈性極高。


常見陷阱與最佳實踐

陷阱 說明 解決方式
變數作用域不明 若在組件內使用 :root 設定變數,會影響全站樣式,可能造成衝突。 使用 scoped 樣式或在組件根元素 (<div class="my-comp">) 上宣告變數,確保僅在該範圍內生效。
變更頻繁導致重排 大量變更 CSS 變數會觸發瀏覽器的 layout 重算,影響效能。 盡量把變數變更集中在一次操作(例如在 requestAnimationFrame 內),或僅在需要時才更改。
過渡與 animation 同時使用 同時設定 transitionanimation 會產生不可預期的行為。 明確分工:transition 用於狀態切換,animation 用於持續或循環效果,避免同一屬性同時被兩者控制。
變數未提供 fallback var(--unknown) 若未定義會返回 unset,可能導致動畫失效。 為重要變數提供 fallback,例如 var(--duration, 300ms)
瀏覽器相容性 老舊瀏覽器(IE)不支援 CSS 變數。 若需支援 IE,考慮使用 PostCSS 插件 postcss-custom-properties 轉譯為靜態值。

最佳實踐

  1. 統一管理變數:在 src/assets/css/variables.css 中集中宣告全局變數,使用 @import 引入。
  2. 使用 :root + prefers-reduced-motion:讓使用者可自行關閉動畫。
    @media (prefers-reduced-motion: reduce) {
      * {
        transition-duration: 0ms !important;
        animation-duration: 0ms !important;
      }
    }
    
  3. 把變數與設計系統結合:將品牌色、間距、圓角等抽成變數,動畫時長也納入系統規範,保持 UI 一致性。
  4. 在 Vue 組件中使用 computed 產生變數值
    const duration = computed(() => `${props.speed}ms`)
    
    並在 :style="{ '--anim-duration': duration }" 中綁定。
  5. 記得清理動態設定的變數:在 onUnmounted 中移除不再需要的 style.setProperty,避免記憶體洩漏。

實際應用場景

場景 需求 建議實作方式
主題切換 亮暗模式切換時需要平滑過渡背景與文字顏色 使用全局 CSS 變數 --bg-color--text-color,在 Vue 中透過 watch 改變 :root,配合 transition
卡片翻轉 點擊卡片時翻轉並改變陰影強度 在卡片根元素上宣告 --rotate-y--shadow,利用 <Transition>enter / leave class 變更變數值。
載入骨架 列表載入時顯示骨架動畫,可根據資料量調整延遲 使用 animation-delay: calc(var(--index) * 100ms),把 --index 由 Vue 迴圈 (v-for) 設定。
操作回饋 按鈕點擊後顯示短暫的波紋或縮放效果 在按鈕的 @click 事件中動態設定 --scale,配合 transition 完成回饋。
彈性彈窗 彈窗高度根據內容自動調整,且有滑入滑出的過渡 透過 <Transition> 的 JavaScript 鉤子,把內容高度寫入 --modal-height,再在 CSS 中 height: var(--modal-height) 並加上 transition

總結

  • CSS Variables 為 Vue3 動畫提供了 簡潔、可參數化 的解決方案。
  • 結合 <Transition> 的 class 鉤子與 JavaScript 生命週期,可在 run‑time 動態設定變數,實現彈性且高效的過渡效果。
  • 在實作時要注意 作用域、效能與相容性,並遵循 變數統一管理、設計系統化 的最佳實踐。

透過本文的概念與範例,你已掌握在 Vue3 中使用 CSS 變數完成動畫的核心技巧。接下來,只要把這些思路套用到自己的專案中,就能為使用者帶來更流暢、更具品牌辨識度的互動體驗。祝開發順利! 🎉