Vue3 Transition 與 Animate.css / GSAP 整合
簡介
在單頁應用程式 (SPA) 中,過渡與動畫不只是視覺上的點綴,更能提升使用者體驗、引導操作流程、降低認知負擔。Vue 3 內建的 <transition>、<transition-group> 組件讓開發者可以輕鬆掛載 CSS 或 JavaScript 動畫,而 Animate.css 與 GSAP 則是兩個在前端社群廣受好評的動畫庫:前者以簡潔的 CSS class 為主,適合快速套用;後者則提供強大的時間軸 (timeline) 與腳本控制,能完成更複雜的交互。
本篇文章將說明如何在 Vue 3 中把這兩個庫結合使用,從最簡單的 class 切換到進階的程式化動畫,並提供完整的程式碼範例、常見陷阱與最佳實踐,讓 初學者 能快速上手、中階開發者 能在專案中穩定運用。
核心概念
1. Vue 3 的過渡機制
Vue 3 透過 <transition>(單一元素)與 <transition-group>(列表)包裹目標元素,然後在元素進入或離開時自動加入/移除特定的 CSS class。預設的 class 名稱如下:
| 階段 | 加入的 class |
|---|---|
| 進入開始 (enter) | v-enter-from → v-enter-active → v-enter-to |
| 離開開始 (leave) | v-leave-from → v-leave-active → v-leave-to |
開發者只要在 CSS 中定義這些 class 的樣式,就能完成動畫。若想使用自訂名稱,只需要在 <transition> 上加上 name="my",對應的 class 會變成 my-enter-*、my-leave-*。
技巧:在 Vue 3 中,
<transition>會自動偵測v-show、v-if、v-for等條件式變化,無需額外的 JavaScript 監聽。
2. 整合 Animate.css
2.1 為什麼選擇 Animate.css?
- 即插即用:只需要在 HTML 元素上加上預設的 class(如
animate__bounceIn)即可。 - 輕量:整個庫只有約 10KB(gzip 後),適合不需要太複雜動畫的專案。
2.2 基本使用方式
- 安裝與匯入
npm install animate.css --save
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import 'animate.css' // 全域匯入
createApp(App).mount('#app')
- 透過
<transition>包裹,配合enter-active-class、leave-active-class
<template>
<button @click="show = !show">Toggle Box</button>
<transition
enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__fadeOut"
>
<div v-if="show" class="box">Hello Vue 3!</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>
<style scoped>
.box {
width: 200px;
height: 100px;
background: #42b983;
color: #fff;
line-height: 100px;
text-align: center;
margin-top: 1rem;
}
</style>
說明:
animate__animated為 Animate.css 必須的基礎 class。enter-active-class、leave-active-class直接對應 Vue 的v-enter-active、v-leave-active,因此不需要自行寫v-enter-from、v-leave-to。
2.3 動態切換不同動畫
<template>
<select v-model="animIn">
<option value="animate__fadeIn">Fade In</option>
<option value="animate__zoomIn">Zoom In</option>
<option value="animate__bounceIn">Bounce In</option>
</select>
<button @click="show = !show">Toggle</button>
<transition
:enter-active-class="`animate__animated ${animIn}`"
leave-active-class="animate__animated animate__fadeOut"
>
<div v-if="show" class="box">切換動畫</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
const animIn = ref('animate__fadeIn')
</script>
重點:使用 模板字串 (
\${}``) 可以把 Vue 的狀態直接注入到 class 名稱,讓使用者即時切換動畫。
3. 整合 GSAP (GreenSock Animation Platform)
3.1 為什麼選擇 GSAP?
- 時間軸控制:允許同時控制多個屬性、序列化動畫、重複與倒放。
- 高效能:底層使用
requestAnimationFrame、硬體加速,對於大量元素或複雜運算仍保持流暢。 - 插件豐富:如
ScrollTrigger、Draggable,可擴展至滾動觸發、拖曳等交互。
3.2 安裝與基本設定
npm install gsap --save
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
注意:GSAP 本身不需要全域匯入 CSS,只要在需要的組件中
import { gsap } from 'gsap'即可。
3.3 使用 Vue 3 的過渡鉤子(hooks)
Vue 允許在 <transition> 中使用 @before-enter、@enter、@after-enter、@enter-cancelled、@before-leave、@leave、@after-leave 等事件,我們可以在這些鉤子裡呼叫 GSAP。
<template>
<button @click="show = !show">Toggle Box</button>
<transition
@enter="enter"
@leave="leave"
>
<div v-if="show" ref="box" class="box">GSAP 動畫</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
import { gsap } from 'gsap'
const show = ref(false)
const box = ref(null)
// 進入動畫
function enter(el, done) {
// el 為即將插入的 DOM,done 為告知 Vue 動畫結束的回呼
gsap.fromTo(
el,
{ opacity: 0, y: -30 },
{
opacity: 1,
y: 0,
duration: 0.6,
ease: 'power2.out',
onComplete: done, // 必須呼叫 done,否則 Vue 會一直認為動畫未結束
}
)
}
// 離開動畫
function leave(el, done) {
gsap.to(el, {
opacity: 0,
y: 30,
duration: 0.4,
ease: 'power2.in',
onComplete: done,
})
}
</script>
<style scoped>
.box {
width: 200px;
height: 100px;
background: #ff6f61;
color: #fff;
line-height: 100px;
text-align: center;
margin-top: 1rem;
}
</style>
重點:
- 必須在
enter、leave兩個鉤子中手動呼叫done,告訴 Vue 動畫已完成。el參數即為目標 DOM,可直接傳給 GSAP。
3.4 使用 GSAP Timeline 建立序列動畫
<template>
<button @click="show = !show">Toggle List</button>
<transition-group
name="list"
tag="ul"
@enter="listEnter"
@leave="listLeave"
>
<li
v-for="item in items"
:key="item.id"
class="list-item"
>{{ item.text }}</li>
</transition-group>
</template>
<script setup>
import { ref, computed } from 'vue'
import { gsap } from 'gsap'
const show = ref(true)
const items = computed(() => {
return show.value
? [
{ id: 1, text: '第一項' },
{ id: 2, text: '第二項' },
{ id: 3, text: '第三項' },
]
: []
})
// 進入序列
function listEnter(el, done) {
const tl = gsap.timeline({ onComplete: done })
tl.from(el, {
opacity: 0,
y: -20,
duration: 0.3,
ease: 'back.out(1.7)',
})
}
// 離開序列(倒序)
function listLeave(el, done) {
const tl = gsap.timeline({ onComplete: done })
tl.to(el, {
opacity: 0,
y: 20,
duration: 0.2,
ease: 'power1.in',
})
}
</script>
<style scoped>
ul {
list-style: none;
padding: 0;
}
.list-item {
background: #4a90e2;
color: #fff;
margin: 0.5rem 0;
padding: 0.5rem;
border-radius: 4px;
}
</style>
說明:
<transition-group>會把每個子元素的enter、leave鉤子分別傳入,讓我們能為每個<li>個別建立時間軸。
3.5 結合 Animate.css 與 GSAP
有時候想先用 Animate.css 做簡單的淡入淡出,接著在 enter 鉤子裡用 GSAP 加上更細緻的彈性動畫,這種混合寫法非常靈活:
<template>
<button @click="show = !show">Toggle Card</button>
<transition
enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__fadeOut"
@after-enter="gsapEnter"
>
<div v-if="show" class="card">混合動畫卡片</div>
</transition>
</template>
<script setup>
import { ref } from 'vue'
import { gsap } from 'gsap'
const show = ref(false)
function gsapEnter(el) {
// Animate.css 已完成淡入,我們再加上微小的縮放彈性
gsap.fromTo(
el,
{ scale: 0.9 },
{ scale: 1, duration: 0.4, ease: 'elastic.out(1, 0.5)' }
)
}
</script>
<style scoped>
.card {
width: 260px;
height: 140px;
background: #7ed321;
color: #fff;
line-height: 140px;
text-align: center;
border-radius: 8px;
margin-top: 1rem;
}
</style>
技巧:
@after-enter在 Animate.css 完成後才觸發,確保兩段動畫不會互相干擾。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
忘記呼叫 done |
在 enter/leave 鉤子裡未執行 done(),會導致過渡卡住、無法觸發 after-enter 等後續事件。 |
確保每個 GSAP 動畫的 onComplete 或 onReverseComplete 都呼叫 done。 |
| Animate.css 與自訂 CSS 衝突 | Animate.css 使用 !important 來保證動畫屬性,若自行寫的 CSS 也使用 !important,可能導致樣式被覆寫。 |
盡量避免在同一屬性上使用 !important,或在自訂 CSS 中使用更高的選擇子權重(例如 .my-box.animate__fadeIn)。 |
| 過渡時間不一致 | Vue 內建的 enter-active / leave-active 持續時間與 Animate.css/GSAP 動畫時間不匹配,會出現「卡頓」或「閃爍」。 |
統一時間:在 <transition> 上使用 duration 屬性或在 GSAP 時間軸裡設定 duration,並在 Animate.css 中使用 animate__duration-2s 等類別。 |
| 大量元素使用 GSAP | 若在 <transition-group> 中一次渲染上百個元素,直接為每個元素建立 Timeline 會產生記憶體與效能問題。 |
使用 批次動畫:先把所有元素放入同一個 Timeline,或使用 gsap.utils.selector 只跑一次 from/to。 |
| SSR (Server‑Side Rendering) 問題 | 在 Nuxt 或 Vite SSR 中,window、document 在伺服器端不存在,直接 import gsap 會拋錯。 |
在 onMounted 之後才載入或呼叫 GSAP,或使用 import('gsap').then(...) 動態載入。 |
最佳實踐
- 統一過渡名稱:若同一個元件內同時使用 CSS 與 JS 動畫,建議在
<transition>上指定name="my",避免與全域的v-enter-*混淆。 - 把動畫邏輯抽成 composable:例如
useFadeTransition.js,裡面封裝enter、leave函式,讓多個元件可以共享。 - 使用
prefers-reduced-motion:對於需要無障礙支援的使用者,應在 CSS 中加上@media (prefers-reduced-motion: reduce) { ... },或在 JS 中檢測window.matchMedia,在必要時停用動畫。 - 保持動畫簡潔:過長或過於炫目的動畫會分散注意力,建議單次過渡控制在 0.2~0.6 秒 之間。
- 測試跨瀏覽器:GSAP 在大多數現代瀏覽器表現一致,但在舊版 IE(若仍需支援)需加入
gsap的 CSSPlugin polyfill。
實際應用場景
| 場景 | 為何需要動畫 | 建議的實作方式 |
|---|---|---|
| 彈出式對話框 (Modal) | 讓使用者感受到聚焦與層級變化,降低突兀感。 | 先用 Animate.css 的 fadeInDown 作淡入,接著 GSAP 加上彈性 scale。 |
| 列表排序或過濾 | 項目移動、加入或刪除時要有視覺提示,避免使用者迷失。 | 使用 <transition-group> 搭配 GSAP Timeline,設定 stagger 讓每個項目依序動畫。 |
| 頁面切換 (Router View) | SPA 內部切換時,給予過渡感,提升流暢度。 | 在 router-view 包裹 <transition>,使用 enter-active-class="animate__slideInRight",leave-active-class="animate__slideOutLeft";若需要更複雜的視差效果,可在 enter 鉤子裡使用 GSAP 的 fromTo。 |
| 滾動觸發動畫 | 用戶瀏覽長頁面時,讓關鍵內容在視窗內出現時動態呈現。 | 結合 GSAP ScrollTrigger 與 <transition>:在 onMounted 時為目標元素建立 ScrollTrigger,觸發時使用 gsap.from。 |
| 表單驗證錯誤提示 | 錯誤訊息需要快速吸引注意力。 | 使用 Animate.css 的 shake 動畫,搭配 v-if 觸發 <transition>;若想要自訂抖動幅度,可在 enter 中使用 gsap.to(el, { x: '+=10', repeat: 5, yoyo: true })。 |
總結
- Vue 3 的
<transition>/<transition-group>為 CSS 與 JavaScript 動畫提供了統一的生命週期介面。 - Animate.css 讓開發者只需加 class 就能得到常見的淡入、彈跳、翻轉等效果,非常適合 快速原型 或 小型專案。
- GSAP 則提供了 時間軸、彈性插值、滾動觸發 等高階功能,適合 複雜交互、大量元素 或 需要精細控制 的場景。
- 透過 Vue 的過渡鉤子,我們可以在 enter / leave 階段自由呼叫 GSAP,並且在 Animate.css 完成後再加入額外的 GSAP 動畫,實現 混合式動畫。
- 常見陷阱多與 同步問題(未呼叫
done)以及 樣式衝突 有關,遵守最佳實踐(統一命名、抽象 composable、考慮 reduced‑motion)即可寫出 可維護、效能佳 的動畫程式碼。
掌握了上述技巧,你就能在 Vue 3 專案中 自如切換 從簡單的 CSS 動畫到強大的 GSAP 時間軸,為使用者打造既流暢又具吸引力的互動體驗。祝開發順利 🎉!