[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

辣手摧花發表於2018-05-15

這是專門探索 JavaScript 及其構建元件系列的第 13 篇文章。在識別和描述核心元素的過程中,我們還分享了構建 SessionStack 時的一些經驗法則,SessionStack 是一個足夠強大且高效能的 JavaScript 應用程式,用來幫助使用者實時檢視和重現其 Web 應用程式的缺陷。

如果你錯過了前面的章節,你可以在這裡找到它們:

  1. [譯] JavaScript 是如何工作的:對引擎、執行時、呼叫堆疊的概述
  2. [譯] JavaScript 是如何工作的:在 V8 引擎裡 5 個優化程式碼的技巧
  3. [譯] JavaScript 是如何工作的:記憶體管理 + 處理常見的4種記憶體洩漏
  4. [譯] JavaScript 是如何工作的: 事件迴圈和非同步程式設計的崛起 + 5個如何更好的使用 async/await 編碼的技巧
  5. [譯] JavaScript 是如何工作的:深入剖析 WebSockets 和擁有 SSE 技術 的 HTTP/2,以及如何在二者中做出正確的選擇
  6. [譯] JavaScript 是如何工作的:與 WebAssembly 一較高下 + 為何 WebAssembly 在某些情況下比 JavaScript 更為適用
  7. [譯] JavaScript 是如何工作的:Web Worker 的內部構造以及 5 種你應當使用它的場景
  8. [譯] JavaScript 是如何工作的:Web Worker 生命週期及用例
  9. [譯] JavaScript 是如何工作的:Web 推送通知的機制
  10. [譯] JavaScript 是如何工作的:用 MutationObserver 追蹤 DOM 的變化
  11. [譯] JavaScript 是如何工作的:渲染引擎和效能優化技巧
  12. [譯] JavaScript 是如何工作的:網路層內部 + 如何優化其效能和安全性

概覽

你也知道,動畫在創造吸引人的 web app 中扮演著重要的角色。隨著使用者越來越多地將注意力轉移到使用者體驗上,商家也開始意識到完美、愉悅的使用者體驗的重要性,web app 變得更加重要,並且 UI 更趨於動效。這一切都需要更復雜的動畫,以便在使用者使用中實現更平滑的狀態轉換。今天,這甚至不被認為是特別的。使用者正在變得越來越挑剔,預設期望高效響應和互動的使用者介面。

但是,動效化你的介面可沒那麼簡單。將什麼做成動畫,什麼時候,做成什麼樣的動畫,都是棘手的問題。

JavaScript 和 CSS 動畫

建立網頁動畫的兩種主要方式是使用 JavaScript 和 CSS。沒有絕對優劣; 一切都取決於你的目標。

CSS 動畫

用 CSS 實現動畫是讓螢幕上的內容移動的最簡單方法。

我們將以一個快速示例說明如何在 X 軸和 Y 軸上移動 50 畫素的元素。通過設定耗時 1000 ms 的 CSS 過渡來完成的。

.box {
  -webkit-transform: translate(0, 0);
  -webkit-transition: -webkit-transform 1000ms;

  transform: translate(0, 0);
  transition: transform 1000ms;
}

.box.move {
  -webkit-transform: translate(50px, 50px);
  transform: translate(50px, 50px);
}
複製程式碼

當類 move 被新增後,transform 的值會改變,過渡開始。

除了過渡的時長,還有其它的用來緩動的選項,這些就是你看到的動畫的本質。稍後我們將在本文中更詳細地討論緩動。

如果像上面的程式碼片段那樣建立單獨的 CSS 類來管理動畫,你就可以使用 JavaScript 來切換每個動畫的開啟和關閉。

假設你有這樣的一個元素:

<div class="box">
  Sample content.
</div>
複製程式碼

然後,你可以使用 JavaScript 切換每個動畫的開啟和關閉:

var boxElements = document.getElementsByClassName('box'),
    boxElementsLength = boxElements.length,
    i;

for (i = 0; i < boxElementsLength; i++) {
  boxElements[i].classList.add('move');
}
複製程式碼

上面的程式碼片段獲取了所有具有 box 類的元素,並新增了 move 類以觸發動畫。

這樣做可以為你的 app 提供很好的平衡。你可以專注於使用 JavaScript 管理狀態,並簡單地在目標元素上設定適當的類,讓瀏覽器處理動畫。如果沿著這條路線走下去,你可以監聽 transitionend 元素上的事件,但前提是你能夠放棄對舊版 Internet Explorer 的支援:

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

監聽 transitioned 過渡結束時觸發的事件,如下所示:

var boxElement = document.querySelector('.box'); // 獲取有 box 類的第一個元素。
boxElement.addEventListener('transitionend', onTransitionEnd, false);

function onTransitionEnd() {
  // 處理過渡完成。
}
複製程式碼

除了 CSS 過渡,你還可以使用 CSS 動畫,它使你對動畫關鍵幀、持續時間、重複有更多的控制。

關鍵幀用於指示瀏覽器在給定點處 CSS 屬性應該有什麼值,並填補(關鍵幀之間的)空白。

我們看個例子:

/**
 * 這是沒有加瀏覽器屬性字首的簡化版本
 * 如果加上,會比較冗長!
 */
.box {
  /* 指定動畫 */
  animation-name: movingBox;

  /* 動畫時長 */
  animation-duration: 2300ms;

  /* 動畫重複次數 */
  animation-iteration-count: infinite;

  /* 動畫正反交替進行 */
  animation-direction: alternate;
}

@keyframes movingBox {
  0% {
    transform: translate(0, 0);
    opacity: 0.4;
  }

  25% {
    opacity: 0.9;
  }

  50% {
    transform: translate(150px, 200px);
    opacity: 0.2;
  }

  100% {
    transform: translate(40px, 30px);
    opacity: 0.8;
  }
}
複製程式碼

它的效果是這樣的(快速演示) — sessionstack.github.io/blog/demos/…

使用 CSS 動畫,你可以獨立於目標元素來定義動畫本身,並使用 animation-name 屬性選擇所需的動畫。

CSS 動畫有時仍然是需要瀏覽器屬性字首的,-webkit- 用於 Safari,Safari Mobile 和 Android。Chrome,Opera,Internet Explorer 和 Firefox 都會在沒有字首的情況下起作用。許多工具可以幫助你建立所需 CSS 的瀏覽器屬性字首,從而允許你在原始檔中編寫無字首的版本。

JavaScript 動畫

與使用 CSS 過渡或動畫相比,使用 JavaScript 建立動畫更復雜,但它通常為開發人員提供了更強大的功能。

JavaScript 動畫是作為程式碼的一部分內聯編寫的。你也可以將它們封裝在其他物件中。下面是你需要用 JavaScript 來編寫的重新建立前面描述的 CSS 過渡:

var boxElement = document.querySelector('.box');
var animation = boxElement.animate([
  {transform: 'translate(0)'},
  {transform: 'translate(150px, 200px)'}
], 500);
animation.addEventListener('finish', function() {
  boxElement.style.transform = 'translate(150px, 200px)';
});
複製程式碼

預設情況下,Web 動畫僅修改元素的顯示。如果你想讓你的物件留在它被移動到的位置,那麼當動畫完成時你應該修改它的底層樣式。這就是為什麼我們要監聽 finish 事件,並將 box.style.transform 屬性設定為 translate(150px, 200px),這與我們動畫的第二個變換相同。

使用 JavaScript 動畫,你可以在每一步完全控制元素的樣式。這意味著你可以放慢動畫,暫停動畫,停止動畫,反轉動畫,並根據需要操作元素。如果你構建複雜的物件導向的 app,這一點尤其有用,因為你可以適當地封裝你的行為。

什麼是緩動?

自然動作讓你的使用者對你的 web app 感到更加舒適,從而帶來更好的使用者體驗。

自然情況下,沒有什麼東西是從一個點到另一個點做線性移動的。事實上,隨著它們在我們周圍的物質世界中移動,事物往往會加速或減速,因為我們並非處於真空狀態,並且存在影響這個因素的不同因素。人類的大腦受制於此會期望這種運動,所以當你為 app 製作動畫時,你應該利用這些知識為你帶來好處。

有一些術語需要了解一下:

  • 「ease-in」 - 開始慢,然後加速。
  • 「ease out」 - 開始快,然後減速。

兩個可以合併,比如「ease in out」。

緩動可以讓動畫感覺起來更自然。

緩動關鍵詞

CSS 過渡和動畫允許你選擇想要使用的緩動型別。有不同的會影響動畫緩動的關鍵詞。當然你完全可以使用自定義的緩動。

以下是你可以在 CSS 中用來控制緩動的一些關鍵詞:

  • linear
  • ease-in
  • ease-out
  • ease-in-out

我們逐一研究,看看究竟是什麼意思。

線性(linear)動畫

沒有任何緩動的動畫稱為線性動畫。

以下是線性過渡的圖示:

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

隨著時間的推移,值會等量增加。使用線性運動時,總會感覺不自然。一般來說,你應該避免線性運動。

這是一個簡單的實現線性動畫的方式:

transition: transform 500ms linear;

緩出(ease-out)動畫

前面已經說過,緩出動畫與線性動畫相比更快地開始,而後變慢。這就是它的圖示:

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

一般來講,緩出是使用者介面工作的最好選擇,因為快速開始給你一種快速響應的感覺,而因為不一致運動在結束時慢下來感覺比較自然。

有很多實現緩出效果的方法,但最簡單的就是使用 CSS 關鍵詞:

transition: transform 500ms ease-out;
複製程式碼

緩入(ease-in)動畫

它與緩出相反 —— 開始慢,結束快。圖示如下:

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

與緩出相比,緩入感覺不太自然,因為它開始慢給人一種無響應的感覺。快速結束也很奇怪,因為整個動畫是在加速,而在現實世界中,物體在忽然停止時往往會減速。

要使用緩入動畫,類似於緩出或者線性動畫,使用關鍵詞:

transition: transform 500ms ease-in;
複製程式碼

緩入緩出(ease-in-out)動畫

它是緩入和緩出的結合,圖示如下:

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

不要用於持續時間過長的動畫,這會讓人覺得你的使用者介面無響應。

使用 CSS 關鍵詞 ease-in-out 實現緩入緩出動畫:

transition: transform 500ms ease-in-out;
複製程式碼

自定義緩動

你可以定義自己的緩動曲線,從而更好地控制專案動畫的形成的感受。

實際上,ease-inease-outlinearease 關鍵詞可以對應到預定義的貝塞爾曲線裡,這在CSS 過渡規範網路動畫規範裡有詳細說明。

貝塞爾曲線

讓我們看一下貝塞爾曲線的工作原理。貝塞爾曲線有四個值,或者更確切地說,它需要兩對數字。每對描述三次貝塞爾曲線控制點的 X 和 Y 座標。貝塞爾曲線的起點座標是 (0, 0),終點座標是 (1, 1)。你可以設定這兩組數。兩個控制點的 X 值必須在 [0, 1] 範圍內,並且每個控制點的 Y 值可以超過 [0, 1] 限制,儘管規範沒有明確說超過多少。

即使每個控制點的 X 和 Y 值發生輕微變化,都會給你一個完全不同的曲線。我們來看看兩個貝塞爾曲線圖,點的座標相近但不同。

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

如你所見,兩個圖區別比較大。兩條曲線的第一個控制點的向量差為 (0.045, 0.183),第二個控制點差 (-0.427, -0.054)。

第二條曲線的 CSS 寫法如下:

transition: transform 500ms cubic-bezier(0.465, 0.183, 0.153, 0.946);
複製程式碼

前兩個數字是第一個控制點的 X 和 Y 座標,後兩個數字是第二個控制點的 X 和 Y 座標。

效能優化

無論何時動畫,你都應該保持 60 fps,否則會對使用者的體驗產生負面影響。

與世界上其他所有的東西一樣,動畫也是有代價的。動畫一些屬性比其他屬性更便宜。例如,動畫修改一個元素的 widthheight 會改變它的形狀,而且可能引起頁面上其它元素的移動和形狀改變。這個過程稱為佈局。我們在之前的一篇文章中已經詳細討論過佈局和渲染。

一般來說,你應該避免使用觸釋出局或繪製的屬性動畫。對於大多數現代瀏覽器,這意味著將動畫(修改的屬性)限制為 opacitytransform.

will-change

你可以使用 [will-change](https://dev.w3.org/csswg/css-will-change/) 通知瀏覽器你打算更改元素的屬性。瀏覽器會在你進行更改之前做最合適的優化。但不要過度使用 will-change,因為這樣做會浪費瀏覽器資源,從而導致更多的效能問題。

可以這樣為變換和不透明度新增 will-change

.box {  will-change: transform, opacity;}
複製程式碼

Chrome,Firefox 和 Opera 的瀏覽器支援非常好。

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

選 JavaScript 還是 CSS?

你可能知道了 —— 這個問題沒有正確或錯誤的答案。你只需要記住以下幾點:

  • 基於 CSS 的動畫和原生支援的 Web 動畫通常在稱為「合成器執行緒」的執行緒上處理。它與瀏覽器的「主執行緒」不同,在該主執行緒中執行樣式,佈局,繪製和 JavaScript。這意味著如果瀏覽器在主執行緒上執行一些耗時的任務,這些動畫可以繼續執行而不會中斷。
  • 在許多情況下,transformsopacity 都可以在合成器執行緒中處理。
  • 如果任何動畫出發了繪製,佈局,或者兩者,那麼「主執行緒」會來完成該工作。這個對基於 CSS 還是 JavaScript 實現的動畫都一樣,佈局或者繪製的開銷巨大,讓與之關聯的 CSS 或 JavaScript 執行工作、渲染都變得毫無意義。

選擇合適的物件來做動畫

優秀的動畫能讓使用者對你的專案的享受和參與感更添一層。無論你是喜歡寬度,高度,位置,顏色還是背景,你可以製作任何你喜歡的任何動畫,但你需要了解潛在的效能瓶頸。選擇不當的動畫會對使用者體驗產生負面影響,因此動畫需要兼具效能和適當性。動畫越少越好。動畫只是為了讓你的使用者體驗感覺自然,但不要過度使用動畫。

用動畫來增強互動

不要只是因為你能就做動畫。相反,使用策略性放置的動畫來增強使用者互動。避免不必要的中斷或阻礙使用者活動的動畫。

避免高代價動畫屬性

唯一比放置得不好的動畫還糟糕的是那些導致頁面卡頓的動畫。這種型別的動畫讓使用者感到沮喪和不快樂。

SessionStack 中使用動畫非常簡單。總的來說,我們遵循上述做法,但由於 UI 的複雜性,我們還有更多利用動畫的場景。SessionStack 必須像視訊一樣重新建立使用者在瀏覽 web app 時遇到問題時發生的所有內容。為此,SessionStack 僅利用會話期間我們的庫收集的資料:使用者事件,DOM 更改,網路請求,異常,除錯訊息等。我們的播放器經過高度優化,可以正確呈現和使用所有收集的內容資料,以便從視覺和技術角度出發,為終端使用者的瀏覽器及其中發生的所有事情提供畫素級的模擬。

為了確保複製得自然,尤其是在長時間和繁重的使用者會話中,我們使用動畫正確指示載入/緩衝,並遵循關於如何實現它們的最佳實踐,以便我們不佔用太多 CPU 時間並讓事件輪詢自由地渲染會話。

如果你想試試 SessionStack,有免費方案哦。

[譯] JavaScript 是如何工作的:CSS 和 JS 動畫背後的原理 + 如何優化效能

資源


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章