過去幾天從多媒體、分享、裝置能力一路走到使用者體驗。今天換個節奏,來一個 畫面動起來 的主題:Web Animations API(WAAPI)。這個 API 讓我們用 JavaScript 直接驅動瀏覽器的動畫引擎,拿到 時間軸、撥放控制、效能優勢,並能和 CSS 動畫互補。💓
如果你有做過動畫,可能習慣兩種方式:
setTimeout
或 requestAnimationFrame
不斷改 style.left
或 style.top
,雖然能控制,但效能差、程式碼也麻煩。WAAPI 結合了兩者的好處:
element.animate(...)
建立動畫,得到一個 Animation 物件。換句話說:WAAPI 讓你既能寫程式控制動畫,又能享受瀏覽器原生的高效能。
前面會花比較多篇幅講原理,想直接看到應用,可以跳到 "基本用法"、"範例 Demo" 🏃♂️
瀏覽器要把一個網頁畫到螢幕上,其實經過好幾個階段:
transform
/ opacity
),交給 GPU 合成畫面。所謂的 compositor,就是第 5 步的「合成器」。瀏覽器的動畫引擎其實包含了許多部分,其中最關鍵、影響效能的就是 compositor。它不會重新計算排版,也不會重畫像素,只負責把已經畫好的圖層 搬動、縮放、旋轉、改透明度,並用 GPU 把畫面「組合」起來,效率非常高。當我們說「交給瀏覽器的動畫引擎(compositor)」時,指的就是這個過程。
left
/width
/background-color
),動畫必須回到主執行緒計算與重畫,一忙就掉幀。transform
和 opacity
,這些屬性能直接交給 compositor 處理。left
、top
、width
、height
這些會觸發 Layout/Paint 的屬性,不然動畫還是得回到主執行緒,容易掉幀。will-change
(但別長期濫用,否則浪費記憶體)。小結:compositor = 瀏覽器的動畫加速器。WAAPI 的強大之處,不只是你能用程式控制時間軸,更在於它能讓動畫直接在 compositor 層執行,做到「控制力 + 高效能」兼得。
還記得嗎~ 在「Day 5 - 已經有 DOM + CSS,為何需要 Canvas!?」我們比較了 DOM + CSS 和 Canvas:
<canvas>
,省掉了 DOM 管理成本。本篇講的 WAAPI,如果動畫的是 transform
/ opacity
等屬性:
👉 兩者雖然都能做動畫,但在流程上的差別是:
因此效能上 WAAPI 的動畫路徑更優,但 Canvas 在需要逐像素控制、大量繪製時仍然不可替代。
前面我們講過,compositor 才是動畫效能的關鍵。那麼 WAAPI 的角色是什麼?簡單來說:
WAAPI = 用 JavaScript 建立並操控一條動畫時間軸,而實際的補間運算與繪製交由瀏覽器的合成器(compositor)在 GPU 層執行。
重點:
Animation
物件=可被暫停、反轉、加速、seek 的時間軸控制器。transform/opacity
,多數情況直接在 compositor 執行,避開 layout/reflow,幀率穩定。fill:'forwards'
只是把最終畫面保留在合成結果,並沒有真的改 DOM 樣式,要自己手動設值才能永久保留。CSS Transition/Animation:
WAAPI:
Animation
物件 → 可程式化控制(時間軸、狀態、事件)。小結:樣式驅動的單純轉場 → CSS;需要時間軸控制/互動耦合 → WAAPI。
<button id="btn">Animate me</button>
<script>
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
const animation = btn.animate(
[
{ transform: 'translateY(0)', opacity: 1 },
{ transform: 'translateY(-8px)', opacity: .8, offset: 0.5 },
{ transform: 'translateY(0)', opacity: 1 }
],
{
duration: 400,
easing: 'cubic-bezier(.2,.8,.2,1)',
iterations: 1,
fill: 'none'
}
);
});
</script>
KeyframeEffect
,每一格定義可動畫的 CSS 屬性(例如 transform、opacity、filter…)。
offset
(0~1):代表在整個時間軸上的相對進度。未指定時,瀏覽器會「平均分佈」各幀(例如 3 幀 → 0、0.5、1)。duration
:動畫時長(毫秒)。easing
:整段動畫的預設補間(若 keyframe 自帶 easing,會覆蓋那一段)。ease
, linear
, ease-in-out
, cubic-bezier(...)
。iterations
:重複次數(數字或 Infinity
)。iterationStart
:從第幾次循環的「部分」開始(例如 0.5 代表從第 1 次的一半開始)。direction
:翻轉播放方向或交錯。normal
| reverse
| alternate
| alternate-reverse
。fill
:是否在動畫前後保留合成結果。none
| forwards
| backwards
| both
。delay / endDelay
:開始前/結束後的延遲(毫秒)。提醒:
fill: 'forwards'
只保留在渲染層,只是把最終畫面保留在合成結果,並沒有真的改 DOM 樣式,若要「真正」定著,請在finished
後手動設值。
Animation
物件const anim = el.animate([...], { duration: 600, fill: 'both' });
anim.pause(); // 暫停
anim.play(); // 播放
anim.reverse(); // 反向播放
anim.cancel(); // 取消並還原
anim.finish(); // 直接跳到最後狀態
anim.playbackRate = 2; // 加速兩倍
await anim.finished; // 以 Promise 等動畫結束
const anim = el.animate([...], { duration: 500 });
anim.onfinish = () => console.log('done');
anim.oncancel = () => console.log('canceled');
anim.finished
.then(() => console.log('finished: resolved'))
.catch(err => console.log('finished: rejected', err.name));
console.log(anim.playState); // 'idle' | 'running' | 'paused' | 'finished'
finished
寫回 style 或加上 class,讓 CSS 接手維持穩態。transform
/ opacity
,避免 width
/height
/left
/top
等會觸發 layout/reflow 的屬性,幀率更穩、耗電更低。const anim = card.animate(
[ { transform:'translateX(-8px)', opacity:.8 }, { transform:'none', opacity:1 } ],
{ duration: 260, fill:'forwards' }
);
anim.finished.then(() => {
card.style.transform = 'none';
card.style.opacity = '1';
});
const a1 = box.animate([{ transform:'translateX(0)' }, { transform:'translateX(60px)' }], { duration: 300, fill:'forwards' });
const a2 = box.animate([{ opacity: .5 }, { opacity: 1 }], { duration: 300, fill:'forwards' });
Promise.all([a1.finished, a2.finished]).then(() => console.log('both done'));
element.animate
,而是先用 KeyframeEffect(target, keyframes, options)
將「作用元素+關鍵幀+選項」封裝成可重用效果。new Animation(effect, document.timeline)
它掛到一條時間軸上建立動畫,play()
播放。ScrollTimeline
)、更容易做序列與同步。const effect = new KeyframeEffect(
el,
[ { opacity:0 }, { opacity:1 } ],
{ duration: 300, fill:'forwards' }
);
const anim = new Animation(effect, document.timeline);
anim.play();
上面我們已經用程式碼展示了 WAAPI 的核心用法(時間軸、控制器、事件)。
如果想直接體驗完整的互動效果,可以參考這個範例:撲克牌堆疊 → 展開 / 收合 / 洗牌動畫。
Animation
的控制能力,讓 UI 在互動中更順、人更有感。👉 歡迎追蹤這個系列,我會從 Canvas 開始,一步步帶你認識更多 Web API 🎯