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

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

若是你錯過了前面的章節,你能夠在這裏找到它們:css

  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 更趨於動效。這一切都須要更復雜的動畫,以便在用戶使用中實現更平滑的狀態轉換。今天,這甚至不被認爲是特別的。用戶正在變得愈來愈挑剔,默認指望高效響應和互動的用戶界面。html

可是,動效化你的界面可沒那麼簡單。將什麼作成動畫,何時,作成什麼樣的動畫,都是棘手的問題。前端

JavaScript 和 CSS 動畫

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

CSS 動畫

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

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

.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 的值會改變,過渡開始。css3

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

若是像上面的代碼片斷那樣建立單獨的 CSS 類來管理動畫,你就可使用 JavaScript 來切換每一個動畫的開啓和關閉。github

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

<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 的支持:

監聽 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)動畫

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

如下是線性過渡的圖示:

隨着時間的推移,值會等量增長。使用線性運動時,總會感受不天然。通常來講,你應該避免線性運動。

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

transition: transform 500ms linear;

緩出(ease-out)動畫

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

通常來說,緩出是用戶界面工做的最好選擇,由於快速開始給你一種快速響應的感受,而由於不一致運動在結束時慢下來感受比較天然。

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

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

緩入(ease-in)動畫

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

與緩出相比,緩入感受不太天然,由於它開始慢給人一種無響應的感受。快速結束也很奇怪,由於整個動畫是在加速,而在現實世界中,物體在突然中止時每每會減速。

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

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

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

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

不要用於持續時間過長的動畫,這會讓人以爲你的用戶界面無響應。

使用 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 值發生輕微變化,都會給你一個徹底不一樣的曲線。咱們來看看兩個貝塞爾曲線圖,點的座標相近但不一樣。

如你所見,兩個圖區別比較大。兩條曲線的第一個控制點的矢量差爲 (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?

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

  • 基於 CSS 的動畫和原生支持的 Web 動畫一般在稱爲「合成器線程」的線程上處理。它與瀏覽器的「主線程」不一樣,在該主線程中執行樣式,佈局,繪製和 JavaScript。這意味着若是瀏覽器在主線程上運行一些耗時的任務,這些動畫能夠繼續運行而不會中斷。
  • 在許多狀況下,transformsopacity 均可以在合成器線程中處理。
  • 若是任何動畫出發了繪製,佈局,或者二者,那麼「主線程」會來完成該工做。這個對基於 CSS 仍是 JavaScript 實現的動畫都同樣,佈局或者繪製的開銷巨大,讓與之關聯的 CSS 或 JavaScript 執行工做、渲染都變得毫無心義。

選擇合適的對象來作動畫

優秀的動畫能讓用戶對你的項目的享受和參與感更添一層。不管你是喜歡寬度,高度,位置,顏色仍是背景,你能夠製做任何你喜歡的任何動畫,但你須要瞭解潛在的性能瓶頸。選擇不當的動畫會對用戶體驗產生負面影響,所以動畫須要兼具性能和適當性。動畫越少越好。動畫只是爲了讓你的用戶體驗感受天然,但不要過分使用動畫。

用動畫來加強交互

不要只是由於你能就作動畫。相反,使用策略性放置的動畫來加強用戶交互。避免沒必要要的中斷或阻礙用戶活動的動畫。

避免高代價動畫屬性

惟一比放置得很差的動畫還糟糕的是那些致使頁面卡頓的動畫。這種類型的動畫讓用戶感到沮喪和不快樂。

SessionStack 中使用動畫很是簡單。總的來講,咱們遵循上述作法,但因爲 UI 的複雜性,咱們還有更多利用動畫的場景。SessionStack 必須像視頻同樣從新建立用戶在瀏覽 web app 時遇到問題時發生的全部內容。爲此,SessionStack 僅利用會話期間咱們的庫收集的數據:用戶事件,DOM 更改,網絡請求,異常,調試消息等。咱們的播放器通過高度優化,能夠正確呈現和使用全部收集的內容數據,以便從視覺和技術角度出發,爲終端用戶的瀏覽器及其中發生的全部事情提供像素級的模擬。

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

若是你想試試 SessionStack,有免費方案哦。

資源


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

相關文章
相關標籤/搜索