前言

用動畫控制 HTML 元素,在 Web 中是不可或缺,卻讓人又愛又恨的功能,想到動畫,腦中先冒出的關鍵字是 DOM animation,或是更常見的 CSS3 Animation,但他們都有一些使用上的「痛點」,前者難以套用 CSS Property,後者的動畫參數難以設定

例如說,想要用滑鼠點擊的座標來計算動畫參數,單純的 CSS3 Animation 就無法滿足我們,另一種方法是透過 JavaScript 產生 animation 後推入 CSSOM,但這樣的方法並不直覺。在一些搜尋之下找到了 Web Animations API 這個方式,他非常簡易使用,且寫法與 CSS3 Animation 如出一轍,讓動畫能夠被有系統地管理。

Web Animations API 目前是一項實驗性功能,他的瀏覽度支援度是這樣子,可以看到大多都是 Partial Support 到 Element.animate,而這項功能本身就很實用。


CSS3 Animation 與它讓人頭痛的地方

1
2
3
4
5
6
7
8
9
.animation {
animation: slide 1s ease-in;
}

@keyframes slide {
from {
transform: translateY(-40px);
}
}

在 CSS3 Animation 中,我們會使用 @keyframes 關鍵字來定義一個動畫的內容,接著透過添加 Class 來將動畫應用在 HTML 元素上,有時候我們會需要「觸發」一個動畫,例如當頁面滑動到該元素出現時,或是滑鼠 hover 到元素上時,這時候可以使用 :hover 等選擇器,或是乾脆使用 JavaScript 來替 DOM 加上 Class。

若我們需要手動 replay 動畫,這時候麻煩的地方就來了,我們得將 Class 移除後再加上去,而且還得用 setTimeout 來延遲(或是其他魔法)才行得通。

另一個麻煩之處是複數動畫的狀況,若動畫是同時播放倒沒什麼問題,但要串接播放的話,我們就需要幫後面的動畫加上 animation-delay ,因為 CSS Animations 預設同時播放,若兩個動畫修改的 property 一樣,則後面的動畫會蓋過前面的動畫,因此我們還需要額外加上 animation-delay 才能將他們連結起來。兩個動畫倒還好,但動畫數量一旦變多,就要不斷重複計算各個動畫的 delay 值,也是一件累事。


初探 Element.animate 方法

這個方法其實是許多方法組合而成的捷徑,首先,他會創建一個 Animation 物件,接著應用這個動畫到該元素上,最後回傳這個 Animation。這個物件可以想像成動畫的「播放器」,我們能夠讀取他目前的播放狀態、重新播放,甚至加上事件監聽。

1
const animation = element.animate(keyframes, options);

Keyframes

這裡的 keyframes 有兩種不同的格式,第一種和我們的 CSS Animation 相似,是以「關鍵影格」的方式來定義,如下面的 Code,將 CSS property 定義在 JavaScript Object 之中,和寫 React 的時候一樣,要將 CSS property 改成 camelCase 的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const moveXAnimation = [
// 關鍵影格 1
{
transform: "translateX(0px)",
},
// 關鍵影格 2
{
transform: "translateX(600px) rotate(360deg)",
}
];

const colorAnimation = [
{
backgroundColor: "#006064", // background-color 改成 camelCase
},
{
backgroundColor: "#80deea",
}
];

每一個關鍵影格都可以額外設定 offset 與 easing ,offset 即是他所佔的動畫時間(以小數表示百分比),easing 則是這一段動畫所使用的 Easing function,設定的方式與 animation-timing-function 相同。

👆如果沒有指定 offset 的話,動畫時間會被平均分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
const moveXAnimation = [
{ // from
transform: "translateX(0px)",
},
{ // 30%
transform: "translateX(300px) rotate(180deg)",
offset: 0.3
},
{ // to
transform: "translateX(600px) rotate(360deg)",
easing: 'ease-out'
}
];

第二種設定方式是以 property 為 key,定義出他的變化量:

1
2
3
4
5
6
7
8
9
const moveXAnimationAlter = {
transform: [
'translateX(0px)', // 0
'translateX(300px) rotate(1800deg)', // 0.3
'translateX(600px) rotate(1800deg)' // 1
],
offset: [0, 0.3],
easing: [ 'ease-in', 'ease-out' ],
}

Options

這裡的設定可以直接放動畫持續的時間(milliseconds),或是更細部的動畫設定,基本上就是 CSS3 Animations 那幾個 properties,我們熟悉的好朋友們:

  • delay, endDelay
  • direction
  • duration
  • easing(這邊的 easing 會蓋過 keyframes 裡面的設定)
  • fill
  • iterations

用 WAAPI 來解決痛點

接下來,我們將前面的動畫用 WAAPI 來改寫,並想辦法解決 CSS Animation 使用上比較不方便的地方。

Replay

重新播放動畫對 WAAPI 來只是一塊蛋糕,畢竟他不是透過添加 Class 去執行動畫,因此沒有 Class 增減時的延遲問題,對 WAAPI 來說,執行動畫就僅是一個指令。

Chain animations

WAAPI 有提供一個叫作 finished 的方法,他是一個當動畫結束時會 resolve 的 Promise 物件,但目前只有 FireFox 實作。那如果想要在 Chrome 上使用怎麼辦呢?因為全部的動作都在 JavaScript 完成,因此我們也可以在動畫的 finish event 上動手。

用 eventListener 的方式將動畫連結起來,這裡因為不希望他直接播放動畫,因此我們手動新增 Animation 物件,前面說過,Animation 物件就像是動畫的播放控制器,因此我們可以控制它什麼時候播放,以及相關的事件處理。

Programmable parameters

WAAPI 另一個讓人無法自拔的好處就是,由於環境都在 JS 中,因此能夠將 JavaScript 變數輕易地放入動畫參數中。也可以將其模組化,比起 CSS Animation 更加容易管理。

1
2
3
4
5
6
7
// Animation creator

export function createSlideInAnimation(translating) {
return {
transform: [`translateX: ${translating}px`]
}
}

結語

WAAPI 的討論從 2015 左右就開始了,由於瀏覽器支援度的關係,至今仍然不是一個適合被用在 product 的功能,不過有官方的 polyfill 可供使用。除了 WAAPI 之外,我也相當期待 Motion Path 這個功能,對於想寫一些簡易動畫又不想載入大量 library 的人來說是一大福音。

參考資料

動畫技術的比較
WAAPI MDN

老實說,我一開始真的只是想做 DIO ㄉ特效而已 …


喜歡這篇文章嗎?

歡迎點擊按鈕分享到 Facebook 上唷!

Weightless Theme
Rocking Basscss