做者:Ollie Williamsjavascript
在 JavaScript 有一個原生動畫 API 叫 Web Animations API,在這篇文章中簡稱爲 WAAPI。MDN 上已經有 很好的文檔,並且,Dan Wilson 爲此寫了 一個很棒的文章系列。java
在本文中,咱們一塊兒來對比一下 WAAPI 和 CSS 動畫。git
儘管瀏覽器原生支持仍然有限,但 WAAPI 有一個全面和強大的 polyfill,使得如今就能在生產環境使用。github
一樣地,能夠在 Can I Use 查看瀏覽器兼容性數據。然而,這並無提供很好的信息來支持 WAAPI 的全部子功能。這裏有一個檢查工具:web
See the Pen WAAPI Browser Support Test by Dan Wilson (@danwilson) on CodePen.api
要想再沒有 polyfill 的狀況下體驗全部功能,請使用 Firefox Nightly。數組
若是你曾經使用 jQuery 的 .animate()
,那麼應該會以爲 WAAPI 的基本語法看起來很熟悉。promise
var element = document.querySelector('.animate-me'); element.animate(keyframes, 1000);
animate
方法接受兩個參數:關鍵幀和持續時間。與 jQuery 相比的優點是,不只是瀏覽器內置,並且性能也更好。瀏覽器
第一個參數,關鍵幀,是一個對象數組,每一個對象都是動畫中的一個關鍵幀。看這個簡單的例子:
var keyframes = [ { opacity: 0 }, { opacity: 1 } ];
第二個參數,持續時間,指的是想要動畫持續多久,在上面的例子中是 1000 毫秒。接下來看一個更使人興奮的例子。
這裏有一些從 animista 拉取的 CSS 代碼,被稱爲 slide-in-blurred-top 的入場動畫。看起來很漂亮
在 實際PERF 比這個 GIF 效果好不少。
如下是 CSS 中的關鍵幀:
0% { transform: translateY(-1000px) scaleY(2.5) scaleX(.2); transform-origin: 50% 0; filter: blur(40px); opacity: 0; } 100% { transform: translateY(0) scaleY(1) scaleX(1); transform-origin: 50% 50%; filter: blur(0); opacity: 1; }
在 WAAPI 中代碼基本相同:
var keyframes = [ { transform: 'translateY(-1000px) scaleY(2.5) scaleX(.2)', transformOrigin: '50% 0', filter: 'blur(40px)', opacity: 0 }, { transform: 'translateY(0) scaleY(1) scaleX(1)', transformOrigin: '50% 50%', filter: 'blur(0)', opacity: 1 } ];
能夠看出,將關鍵幀應用到須要動畫的元素上是多麼容易:
element.animate(keyframes, 700);
爲了簡單起見,只指定了持續時間。可是,咱們可使用這個第二個參數來傳遞更多的選項,至少也應該指定一個緩動效果。如下是全部可用選項的完整列表,其中包含一些示例值:
var options = { iterations: Infinity, iterationStart: 0, delay: 0, endDelay: 0, direction: 'alternate', duration: 700, fill: 'forwards', easing: 'ease-out', } element.animate(keyframes, options);
加上這些選項,咱們的動畫將從頭開始,沒有任何延遲,在動畫完成後往返循環播放。
不爽的是,對於熟悉 CSS 動畫的人來講,一些術語跟咱們習慣的有所不一樣。好處是打字會更快一些!
easing
而不是 animation-timing-function
animation-iteration-count
,而是 iterations
。若是咱們但願動畫永遠重複,使用 Infinity
而不是 infinite
。有點混亂, Infinity
不帶引號。Infinity
是一個 JavaScript 關鍵字,而其餘值都是字符串。咱們來仔細看看一個選項:iterationStart
。
當我第一次碰到 iterationStart
有點困惑。爲何要從指定的迭代開始,而不是隻要減小迭代次數?當使用十進制數時,此選項很是有用。例如,能夠將其設置爲 .5
,動畫將開始一半。要作一個完整的動畫須要兩個一半,因此若是迭代次數設置爲 1,而且將 iterationStart
設置爲 .5
,動畫將從一半到動畫結束播放,而後從動畫開頭開始,結束於中間!
值得注意的是,也能夠將迭代次數設置爲小於 1。例如:
var option = { iterations: .5, iterationStart: .5 }
這樣,動畫會從中間開始,一直播放到最後。
endDelay:若是要將多個動畫串在一塊兒,可是但願在一個動畫的結尾和後續動畫的開始之間存在差距,這時 endDelay 就頗有用。這是一個有用的視頻,由 Patrick Brosset 來解釋。
在任何動畫中,緩動都是最重要的元素之一。WAAPI 爲咱們提供了兩種不一樣的方式設置緩動 - 在咱們的關鍵幀數組或咱們的選項對象內。
在 CSS 中,若是使用 animation-timing-function: ease-in-out
你可能會認爲動畫會緩慢開始,而後緩慢結束。實際上,這些緩動應用在關鍵幀之間,而不是整個動畫。這能夠對動畫的感受進行細粒度的控制。WAAPI 也提供這種功能。
var keyframes = [ { opacity: 0, easing: 'ease-in' }, { opacity: 0.5, easing: 'ease-out' }, { opacity: 1 } ]
值得注意的是,在 CSS 和 WAAPI 中,不該該傳入最後一幀的緩動值,由於這將不起做用。但是不少人會犯這種錯誤。
有時候,在整個動畫中添加緩動效果更爲直觀。這在 CSS 是不可能的,但如今能夠在 WAAPI 中實現。
var options = { duration: 1000, easing: 'ease-in-out', }
能夠看到這兩種緩動在 CodePen 上的區別:
值得注意的是 CSS 動畫和 WAAPI 之間的另外一個區別:在 CSS 中 默認值是 ease,而在 WAAPI 默認是 linear。 ease 其實是 ease-in-out
的一個版本,當你想偷懶時這是一個很是好的選擇。同時,線性表明致命的沉悶和無生命 - 一致的速度看起來機械和不天然。它被選爲默認值,可能由於它是最中立的選項。然而,在使用 WAAPI 時,更好是使用緩動,以避免動畫看起來很乏味和機械。
WAAPI 提供與 CSS 動畫相同的性能改進,儘管這並不意味着必定就是平滑的動畫。
但願這個 API 的性能優化作到,使咱們能夠避免使用 will-change
和 translateZ
成爲可能。可是,至少在目前的瀏覽器實現中,這些屬性在處理性能問題方面仍然是有幫助,有必要的。
可是,若是你的動畫有延遲,則無需擔憂使用 will-change
。web animations 規範的主要做者對 Animation for Work Slack community 提出了一些有趣的建議,但願他不介意我在這裏重複:
若是有一個正向的延遲,不須要使用
will-change
,由於瀏覽器將在延遲開始時進行分層,當動畫啓動時,它將準備就緒。
WAAPI 爲咱們提供了一套已經在 CSS 中實現的 JavaScript 語法。然而,它們不該該被視爲對手。若是咱們堅持使用 CSS 完成動畫和轉換,那麼咱們能夠在 WAAPI 進行動畫交互。
.animate()
方法不只處理元素的動畫,它也返回一些東西。
var myAnimation = element.animate(keyframes, options);
在控制檯中查看的動畫對象
若是咱們在控制檯中查看返回值,會發現這是一個動畫對象。這爲咱們提供了各類各樣的功能,其中一些是不言自明,好比 myAnimation.pause()
。經過更改 animation-play-state
屬性,咱們能夠經過 CSS 動畫實現相似的結果,但 WAAPI 語法比 element.style.animationPlayState = "paused"
更簡潔。咱們也能夠經過 myAnimation.reverse()
輕鬆反轉動畫,一樣地,跟咱們使用腳本更改 CSS 的 animation-direction
屬性相比,稍微有點進步。
然而,到目前爲止,使用 JavaScript 操做 @keyframe
並非件容易的的事。即便是從新啓動動畫這樣簡單的事,也是須要一些技巧的,就像 Chris Coyier 先前寫過的那樣。使用 WAAPI,咱們能夠簡單地使用 myAnimation.play()
,若是動畫已經完成,將從一開始就重播動畫,或者若是咱們暫停播放,則從中間迭代播放動畫。
咱們甚至能夠輕鬆地改變更畫的速度。
myAnimation.playbackRate = 2; // speed it up myAnimation.playbackRate = .4; // use a number less than one to slow it down
這個方法將返回全部動畫對象的數組,包含使用 WAAPI 定義的動畫和 CSS 轉換或動畫。
element.getAnimations() // returns any animations or transitions applied to our element using CSS or WAAPI
若是你喜歡使用 CSS 來定義和使用動畫,getAnimations()
容許 API 與 @keyframes
結合使用。你能夠繼續使用 CSS 進行大部分動畫工做,而後在須要 API 時得到使用 API 的優點。
即便一個 DOM 元素只使用到一個動畫,getAnimations()
也將始終返回一個數組。咱們使用那個單一的動畫對象來處理。
var h2 = document.querySelector("h2"); var myCSSAnimation = h2.getAnimations()[0];
咱們也能夠在 CSS 動畫中使用 web animation API :)
myCSSAnimation.playbackRate = 4; myCSSAnimation.reverse();
不少經過 CSS 觸發的事件,如今咱們已經可使用 JavaScript 代碼來完成: animationstart
,animationend
,animationiteration
和 transitionend
。以前常常須要監聽動畫或轉換的結束,以便從 DOM 中刪除應用的元素。
在動畫對象可使用 WAAPI 來完成 animationend
或 transitionend
作的事情:
myAnimation.onfinish = function() { element.remove(); }
WAAPI 爲咱們提供了兩個選擇:event 和 promise。動畫對象的 .finished
方法會返回一個在動畫結束時的 promise。下面這段代碼是上面例子的 promise 版本:
myAnimation.finished.then(() => element.remove())
咱們來看看來自 Mozilla 開發者網絡中的一個稍微複雜點的例子。Promise.all
接受一個 promise 的數組,一旦全部 promise 完成纔會運行回調函數。能夠看出,element.getAnimations()
返回的是一個動畫對象數組。咱們能夠將數組中的全部動畫對象 map 到每一個動畫對象的 .finished
上,這樣就得到須要的 promise 數組。
在這個例子中,只有在頁面上的全部動畫完成後,咱們的函數才能運行。
Promise.all(document.getAnimations().map(animation => animation.finished)).then(function() { // do something cool })
本文中提到的功能只是一個開始。從目前的規範和實施來看,將來會有一個很強大動畫 API。