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> 中直接操作 |
全域/局部:可在根元素或單一組件內定義 |
生命週期鉤子:onBeforeEnter、onEnter 等 |
過渡:配合 transition、animation 使用 |
將 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>
說明:
animation的duration直接由--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 同時使用 | 同時設定 transition 與 animation 會產生不可預期的行為。 |
明確分工:transition 用於狀態切換,animation 用於持續或循環效果,避免同一屬性同時被兩者控制。 |
| 變數未提供 fallback | var(--unknown) 若未定義會返回 unset,可能導致動畫失效。 |
為重要變數提供 fallback,例如 var(--duration, 300ms)。 |
| 瀏覽器相容性 | 老舊瀏覽器(IE)不支援 CSS 變數。 | 若需支援 IE,考慮使用 PostCSS 插件 postcss-custom-properties 轉譯為靜態值。 |
最佳實踐
- 統一管理變數:在
src/assets/css/variables.css中集中宣告全局變數,使用@import引入。 - 使用
:root+prefers-reduced-motion:讓使用者可自行關閉動畫。@media (prefers-reduced-motion: reduce) { * { transition-duration: 0ms !important; animation-duration: 0ms !important; } } - 把變數與設計系統結合:將品牌色、間距、圓角等抽成變數,動畫時長也納入系統規範,保持 UI 一致性。
- 在 Vue 組件中使用
computed產生變數值:
並在const duration = computed(() => `${props.speed}ms`):style="{ '--anim-duration': duration }"中綁定。 - 記得清理動態設定的變數:在
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 變數完成動畫的核心技巧。接下來,只要把這些思路套用到自己的專案中,就能為使用者帶來更流暢、更具品牌辨識度的互動體驗。祝開發順利! 🎉