精準控制動畫流:深入 Web Animations API (WAAPI)
在網頁開發中,我們習慣使用 CSS Transitions 或 Animations 來實現視覺效果。然而,當動畫變得複雜(例如:需要動態計算路徑、精確的播放控制、或是在動畫結束後執行複雜邏輯)時,CSS 就會顯得力不從心。
這就是 Web Animations API (WAAPI) 大顯身手的地方。它結合了 CSS 的效能與 JavaScript 的靈活性,讓我們能用程式碼完整掌控動畫的生命週期。
一、 為什麼選擇 Web Animations API?
- 程式化控制:支援
play()、pause()、reverse()、finish()等方法。 - 效能最佳化:與 CSS 動畫一樣,運行在瀏覽器的合成器執行緒(Compositor Thread),避免阻塞主執行緒。
- 動態屬性:關鍵影格(Keyframes)可以用 JavaScript 動態產生,不需預先寫死在 CSS 中。
- Promise 支援:原生的
finished屬性是一個 Promise,方便串接多個動畫或回呼函數。
二、 WAAPI 核心基礎
2.1 基本語法
javascript
const animation = element.animate(
// 1. 關鍵影格 (Keyframes)
[
{ transform: 'translateX(0)', opacity: 0 },
{ transform: 'translateX(100px)', opacity: 1 }
],
// 2. 動畫選項 (Timing Options)
{
duration: 1000,
iterations: Infinity,
easing: 'ease-in-out'
}
);2.2 控制方法
javascript
animation.pause(); // 暫停
animation.play(); // 播放
animation.reverse(); // 倒放
animation.cancel(); // 取消並重置三、 封裝 useAnimate Composable
為了讓動畫在 Vue 元件中更易於管理,我們實作一個 useAnimate。
3.1 完整實作
typescript
// composables/useAnimate.ts
import { ref, onUnmounted, type Ref } from 'vue';
export interface AnimateOptions extends KeyframeAnimationOptions {
immediate?: boolean;
}
export function useAnimate(
target: Ref<HTMLElement | null>,
keyframes: Keyframe[] | PropertyIndexedKeyframes,
options: AnimateOptions = {}
) {
const { immediate = true, ...animationOptions } = options;
const animation = ref<Animation | null>(null);
const playState = ref<AnimationPlayState>('idle');
const play = () => {
if (!target.value) return;
// 如果動畫不存在或已被取消,則建立新動畫
if (!animation.value || animation.value.playState === 'finished') {
animation.value = target.value.animate(keyframes, animationOptions);
// 監聽狀態變化
animation.value.onfinish = () => (playState.value = 'finished');
animation.value.oncancel = () => (playState.value = 'idle');
} else {
animation.value.play();
}
playState.value = 'running';
};
const pause = () => {
animation.value?.pause();
playState.value = 'paused';
};
const reverse = () => {
animation.value?.reverse();
playState.value = 'running';
};
const cancel = () => {
animation.value?.cancel();
playState.value = 'idle';
};
// 如果設定了 immediate,在組件掛載或 target 準備好時播放
if (immediate) {
// 這裡可以使用 watch 或 onMounted 確保 target 已掛載
}
onUnmounted(() => cancel());
return {
animation,
playState,
play,
pause,
reverse,
cancel,
finished: animation.value?.finished
};
}四、 實戰範例:進場動畫與手動控制
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useAnimate } from '@/composables/useAnimate';
const boxRef = ref<HTMLElement | null>(null);
const { play, pause, reverse, playState } = useAnimate(
boxRef,
[
{ transform: 'scale(0.5) rotate(0deg)', borderRadius: '50%' },
{ transform: 'scale(1.2) rotate(180deg)', borderRadius: '10%' },
{ transform: 'scale(1) rotate(360deg)', borderRadius: '20%' }
],
{
duration: 2000,
fill: 'forwards',
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
}
);
</script>
<template>
<div class="demo-container">
<div ref="boxRef" class="animated-box">Vue + WAAPI</div>
<div class="controls">
<p>當前狀態:{{ playState }}</p>
<button @click="play">播放 / 繼續</button>
<button @click="pause">暫停</button>
<button @click="reverse">倒放</button>
</div>
</div>
</template>
<style scoped>
.animated-box {
width: 150px;
height: 150px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
margin: 50px auto;
font-weight: bold;
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
.controls {
text-align: center;
gap: 10px;
}
</style>五、 技術深度解析
5.1 動態 Keyframes 的威力
不同於 CSS,你可以根據使用者的滑鼠位置或目前的資料狀態,動態生成下一組動畫:
javascript
const dynamicKeyframes = [
{ left: `${currentX}px` },
{ left: `${targetX}px` }
];
element.animate(dynamicKeyframes, 500);5.2 瀏覽器支援狀況
WAAPI 已在主流瀏覽器中獲得良好支援:
| 特性 | Chrome | Edge | Firefox | Safari |
|---|---|---|---|---|
| 基礎核心 | 支援 | 支援 | 支援 | 支援 |
finished Promise | 支援 | 支援 | 支援 | 支援 |
composite 操作 | 支援 | 支援 | 支援 | 部分支援 |
總結
Web Animations API 填補了 CSS 與 JavaScript 動畫之間的鴻溝。透過 useAnimate Composable,我們能夠在 Vue 的宣告式世界中,精確地、程序化地控制每一個關鍵影格,創造出更為流暢且具備互動性的使用者體驗。