使用 Web 動畫 API 編排複雜性

已發表: 2022-03-10
快速總結↬ Web Animations API 中的選項太多了,無法輕鬆地選擇它們。 了解計時的工作原理以及如何同時控制多個動畫的播放為您的項目奠定堅實的基礎。

簡單的過渡和復雜的動畫之間沒有中間地帶。 你要么對 CSS 過渡和動畫提供的東西很好,要么你突然需要你能得到的所有力量。 Web Animations API 為您提供了許多處理動畫的工具。 但是你需要知道如何處理它們。 本文將引導您了解可以幫助您處理複雜動畫同時保持靈活性的要點和技術

在我們深入研究本文之前,您必須熟悉 Web Animations API 和 JavaScript 的基礎知識。 為了清楚起見並避免分心手頭的問題,提供的代碼示例很簡單。 沒有比函數和對象更複雜的了。 作為動畫本身的不錯切入點,我建議 MDN 作為一般參考,Daniel C. Wilson 的優秀系列,以及 Ollie Williams 的 CSS Animations vs Web Animations API。 我們不會通過定義效果和調整它們以達到您想要的結果的方法。 本文假設您已定義動畫並需要處理它們的想法和技術。

我們首先概述接口及其用途。 然後,我們將查看控制時間和級別,以定義什麼、何時以及持續多長時間。 之後,我們將學習如何通過將多個動畫包裝在對像中來將它們視為一個。 這將是您使用 Web Animations API 的良好開端。

接口

Web Animations API 為我們提供了一個新的控制維度。 在此之前,CSS 過渡和動畫雖然提供了一種定義效果的強大方式,但仍然具有單點驅動。 就像電燈開關一樣,它不是開就是關。 您可以使用延遲和緩動函數來創建相當複雜的效果。 儘管如此,在某些時候,它會變得繁瑣且難以使用。

Web Animations API 將這個單點驅動轉變為對播放的完全控制。 電燈開關變成帶滑塊的調光開關。 如果你願意,你可以把它變成整個智能家居的東西,因為除了播放控制之外,你現在可以在運行時定義和更改效果。 您現在可以根據上下文調整效果,或者您可以實現具有實時預覽的動畫編輯器。

我們從動畫界面開始。 要獲取動畫對象,我們可以使用Element.animate方法。 你給它關鍵幀和選項,它會立即播放你的動畫。 它還做的是返回一個Animation對象實例。 其目的是控製播放。

如果你記得這些,可以把它想像成一個磁帶播放器。 我知道有些讀者可能不熟悉它是什麼。 任何試圖應用現實世界的概念來描述抽象的計算機事物的嘗試都不可避免地會很快失敗。 但是讓你放心——一個不知道用鉛筆倒帶的樂趣的讀者——知道什麼是磁帶播放器的人會在本文結束時更加困惑。

想像一個盒子。 它有一個放磁帶的插槽,還有播放、停止和倒帶的按鈕。 這就是 Animation 接口實例 - 一個包含已定義動畫並提供與其播放交互的方法的框。 你給它一些玩的東西,它給你回控制權。

您獲得的控件方便地類似於您從音頻和視頻元素獲得的控件。 它們是播放暫停方法,以及當前時間屬性。 使用這三個控件,您可以在播放方面構建任何內容。

卡帶本身是一個包,其中包含對動畫元素的引用、效果的定義以及包括時間在內的選項。 這就是KeyframeEffect的含義。 我們的盒式磁帶可以保存所有錄音和有關錄音長度的信息。 我將把所有這些屬性與物理磁帶的組件相匹配,留給年長的觀眾想像。 我將向您展示的是它在代碼中的樣子。

當您通過Element.animate創建動畫時,您正在使用執行三件事的快捷方式。 它創建一個KeyframeEffect實例。 它放入一個新的Animation實例中。 它立即開始播放。

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

讓我們分解一下,看看做同樣事情的等效代碼。

 const animation = new Animation( // (2) new KeyframeEffect(element, keyframes, options) // (1) ); animation.play(); (3)

獲取磁帶 (1),將其放入播放器 (2),然後點擊播放按鈕 (3)。

了解它在幕後如何工作的關鍵在於能夠分離關鍵幀的定義並決定何時播放它。 當您有很多動畫要協調時,首先將它們全部收集起來可能會有所幫助,這樣您就知道它們已經準備好播放了。 即時生成它們並希望它們在正確的時刻開始播放並不是您希望的事情。 拖幾幀就很容易破壞想要的效果。 如果拖累累積的長序列導致完全沒有令人信服的經驗。

跳躍後更多! 繼續往下看↓

定時

與喜劇一樣,時間就是動畫中的一切。 要使效果起作用,要獲得某種感覺,您需要能夠微調屬性更改的方式。 您可以在 Web Animations API 中控制兩個級別的時間

在單個屬性的級別上,我們有offset 。 Offset 讓您可以控制單個屬性的時間。 通過給它一個從零到一的值,您可以定義每個效果何時生效。如果省略,它等於零。

您可能還記得 CSS 中的@keyframes如何使用百分比而不是from / to 。 這就是offset除以一百。 offset的值是單次迭代持續時間的一部分

offset允許您在KeyframeEffect中排列關鍵幀。 作為一個相對數字偏移量,可以確保無論播放的持續時間或速率如何,所有關鍵幀都在相對於彼此的同一時刻開始。

如前所述, offsetduration 的一部分。 現在我希望你避免我的錯誤和在這方面浪費時間。 重要的是要了解動畫的持續時間與動畫的整體持續時間不同。 通常,它們是相同的,這可能會讓您感到困惑,而這肯定會讓我感到困惑。

持續時間是完成一次迭代所需的時間量(以毫秒為單位)。 默認情況下,它將等於總持續時間。 一旦您添加延遲或增加動畫持續時間中的迭代次數,就會停止告訴您您想知道的數字。 理解這一點很重要,以便將其用於您的優勢。

當您需要在更大的上下文中協調關鍵幀播放時,例如媒體播放,您需要使用計時選項。 動畫從開始到“完成”事件的整個持續時間如下等式:

 delay + (iterations × duration) + end delay

您可以在以下演示中看到它的實際效果:

請參閱 Kirill Myshkin 的 Pen [動畫的實際持續時間是多少?](https://codepen.io/smashingmag/pen/VwWWrzz)。

查看 Pen 動畫的實際持續時間是多少? 基里爾·米甚金(Kirill Myshkin)。

這允許我們在固定長度媒體的上下文中對齊多個動畫。 保持所需的動畫持續時間不變,您可以在開始時用delaydelayEnd ”它,在結束時用延遲結束來“填充”它,以便將其嵌入到持續時間更長的上下文中。 如果您考慮一下,從這個意義上講, delay就像關鍵幀中的偏移一樣。 請記住延遲以毫秒為單位設置,因此您可能希望將其轉換為相對值。

另一個有助於對齊動畫的計時選項是iterationStart 。 它設置迭代的起始位置。 以台球演示為例。 通過調整iterationStart滑塊,您可以設置小球的起始位置和旋轉,例如,您可以設置它從屏幕中心開始跳躍,並使數字在最後一幀的相機中保持直線。

請參閱 Kirill Myshkin 的 Pen [Tweak interationStart](https://codepen.io/smashingmag/pen/qBjjVPR)。

請參閱 Kirill Myshkin 的 Pen Tweak interationStart。

多管齊下

當我為演示應用程序製作動畫編輯器時,我必須為時間軸上的單個元素安排多個動畫。 我的第一次嘗試是使用offset將我的動畫放在時間軸上的正確起點。

這很快被證明是使用offset的錯誤方式。 就時間軸上這個特定的 UI 移動動畫而言,它意味著在不改變動畫持續時間的情況下改變其起始位置。 使用offset意味著我需要更改一些東西,即offset本身並更改關閉屬性的offset以確保持續時間不會改變。 該解決方案被證明太複雜而無法理解。

第二個問題來自transform屬性。 由於它可以代表一個元素的幾個特徵變化,讓它做你想做的事情會變得很棘手。 如果希望彼此獨立地更改這些屬性,則可能會變得更加困難。 尺度函數的變化會影響它之後的所有函數。 這就是發生這種情況的原因。

Transform 屬性可以將序列中的多個函數作為值。 根據函數的順序,結果會發生變化。 採取scaletranslate 。 有時用百分比定義translate很方便,這意味著相對於元素的大小。 假設你想讓一個球恰好跳出三個自身直徑的高度。 現在,根據您放置縮放功能的位置(在translate之前或之後),結果會從原始大小的三個高度或縮放的高度發生變化。

它是transform屬性的一個重要特徵。 您需要它來實現相當複雜的轉換。 但是,當您需要這些轉換不同且獨立於元素的其他轉換時,它會妨礙您。

在某些情況下,您無法將所有效果放在一個transform屬性中。 它很快就會變得太多。 特別是如果您的關鍵幀來自不同的地方,您將需要對轉換後的字符串進行非常複雜的合併。 您幾乎不能依賴自動機制,因為邏輯並不簡單。 此外,可能很難理解會發生什麼。 為了簡化這一點並保持靈活性,我們需要將它們分成不同的渠道。

一種解決方案是將我們的元素包裝到div中,每個都可以單獨設置動畫,例如,一個 div 用於在畫布上定位,另一個用於縮放,第三個用於旋轉。 這樣,您不僅可以大大簡化動畫的定義,還可以在適用的情況下定義不同的變換原點。

看起來事情會因為這個把戲而失控。 我們正在成倍增加之前遇到的問題。 事實上,當我第一次發現這個技巧時,我認為它太過分了。 我認為我可以確保我的transform屬性是按照正確的順序從所有部分中編譯出來的。 它需要一個額外的transform功能才能使事情變得過於復雜而無法管理,並且某些事情無法做到。 我的transform屬性字符串編譯器開始花費越來越多的時間才能正確,所以我放棄了。

事實證明,控制多個動畫的播放並不像最初看起來那麼難。 還記得一開始就對盒式磁帶播放器進行類比嗎? 如果您可以使用自己的播放器來接收任意數量的磁帶會怎樣? 不僅如此,您還可以在該播放器上添加任意數量的按鈕。

在單個動畫和動畫數組上調用play的唯一區別是您需要迭代。 這是您可以用於任何Animation實例方法的代碼:

 // To play just call play on all of them animations.forEach((animation) => animation.play());

我們將使用它為我們的播放器創建各種功能。

讓我們創建一個盒子來保存動畫並播放它們。 您可以以任何合適的方式創建這些框。 為了清楚起見,我將向您展示一個使用函數和對象執行此操作的示例。 createPlayer函數採用要同步播放的動畫數組。 它返回一個具有單一play方法的對象。

 function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); } }); }

這足以讓您知道開始擴展功能。 讓我們添加 pause 和currentTime方法。

 function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); }, pause: function () { animations.forEach((animation) => animation.pause()); }, currentTime: function (time = 0) { animations.forEach((animation) => animation.currentTime = time); } }); }

具有這三個方法的createPlayer為您提供了足夠的控制來編排任意數量的動畫。 但是,讓我們再往前推一點。 讓我們做到這一點,這樣我們的播放器不僅可以帶任意數量的磁帶,還可以帶其他播放器。

正如我們之前看到的, Animation接口類似於媒體接口。 使用這種相似性,您可以在播放器中放置各種東西。 為了適應這種情況,讓我們調整currentTime方法,使其適用於動畫對象和來自createPlayer的對象。

 function currentTime(time = 0) { animations.forEach(function (animation) { if (typeof animation.currentTime === "function") { animation.currentTime(time); } else { animation.currentTime = time; } }); }

我們剛剛創建的播放器可以讓您隱藏多個div用於單元素動畫通道的複雜性。 這些元素可以組合在一個場景中。 每個場景都可能是更大事物的一部分。 所有這一切都可以用這種技術來完成。

為了演示計時演示,我將所有動畫分成三個播放器。 第一個是控制右側預覽的播放。 第二個結合了左側所有球輪廓的跳躍動畫和預覽中的一個。

最後,第三個是結合左容器中球的位置動畫的球員。 該播放器允許球以每秒約 60 幀的切片速度在動畫的連續演示中傳播。

結論

像 Web Animations API 這樣的 Web 界面向我們展示了瀏覽器一直以來所做的某些事情。 瀏覽器知道如何通過將工作傳遞給 GPU 來快速渲染。 使用 Web Animations API,我們可以控制它。 儘管該控件可能看起來有點陌生或令人困惑,但這並不意味著使用它也應該令人困惑。 了解時間和回放控制後,您就有了工具來根據您的需要來調整 API。 您應該能夠定義它應該有多複雜。

延伸閱讀

  • “設計動畫的實用技巧,”莎拉·德拉斯納
  • “針對運動靈敏度進行減少運動設計”,Val Head
  • “語音助手的替代語音 UI”,Ottomatias Peura
  • “為移動用戶界面設計更好的工具提示”,Eric Olive