JavaScript 工做原理之十三-CSS 和 JS 動畫底層原理及如何優化其性能

原文請查閱這裏,本文采用知識共享署名 4.0 國際許可協議共享,BY Trolandjavascript

本系列持續更新中,Github 地址請查閱這裏css

這是 JavaScript 工做原理的第十三章。html

概述

正如你所知,動畫在建立使人歎服的網絡應用中扮演着一個關鍵角色。因爲用戶愈來愈注重用戶體驗,商戶開始意識到完美,使人愉悅的用戶體驗的重要性,結果網絡應用變得愈來愈重而且擁有更多動態交互的功能。這就要求網絡應用提供更加複雜的動畫來實現平滑的狀態過渡貫穿於用戶的使用過程中。如今,這已經司空見慣。用戶變得愈來愈挑剔,他們潛意識期許能夠得到快速響應和良好交互的用戶界面。java

然而,讓界面具備動畫效果不必定是件簡單的事情。動畫的時機,方面及採用何種動畫效果都是很模糊的概念。css3

JavaScript 和 CSS 動畫比較

JavaScript 和 CSS 是建立網頁動畫的兩條主要途徑。兩種不分好賴,看狀況用吧。git

CSS 動畫

使用 CSS 動畫是讓元素在屏幕上移動的最簡單方法。github

咱們將會以如何讓元素在 X 和 X 座標上移動元素 50 像素做爲小示例開始。經過持續 1 秒的 CSS 過渡來移動元素。web

.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 的值而後開發發生過渡效果。瀏覽器

除了過渡持續時間,還有 easing 參數,它主要負責動畫體驗。該參數會在以後詳細介紹。性能優化

若是經過以上的代碼片斷能夠建立單獨的樣式類來操做動畫,那麼也可使用 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 類來觸發動畫。

這樣作能夠很好爲你的網絡應用提供很好的平衡。你就能夠專一於使用 JavaScript 來操做應用狀態,而後只需爲目標元素設置合適的類,讓瀏覽器來處理動畫。如若你選擇這麼處理,就能夠監聽元素的 transitionend 事件,除了處理IE 老版本瀏覽器兼容問題以外。

以下監聽 transitioned 事件,該事件會在動畫結束時觸發。

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

function onTransitionEnd() {
  // Handle the transition finishing.
}
複製代碼

除了使用 CSS 過渡,還可使用 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 動畫仍然是須要加瀏覽器前綴的,在 Safari, Safari 移動瀏覽器和 Android 端添加 -webkit- 前綴。Chrome, Opera, Internet Explorer, and Firefox 端所有不須要添加前綴。有不少工具能夠用來建立包含任意前綴的樣式,這樣就不須要在源文件中帶樣式前綴。

可使用 autoprefixer 或者 cssnext 來自動爲樣式添加前綴。

JavaScript 動畫

和 CSS 過渡或者 CSS 動畫相比,使用 JavaScript 來建立動畫要更加複雜些,可是通常而言,它會爲開發進行提供強大的功能。

通常狀況下,能夠內聯 JavaScript 動畫做爲代碼的一部分。也能夠把它們封裝在其它對象之中。如下爲復現以前描述的 CSS 過渡的 JavaScript 代碼:

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)';
});
複製代碼

默認狀況下,網頁動畫只是修改了元素的展現效果。若是想要讓元素停留在其移動到的目標位置,那麼就得在動畫結束的時候修改其底層樣式。這也是爲何在以上的示例中監聽 finish 事件而後設置box.style.transform 屬性爲 translate(150px, 200px) 的緣由,該屬性值和 CSS 動畫執行的第二個樣式轉換是同樣的。

經過使用 JavaScript 動畫,能夠徹底控制每一步元素的樣式。這意味着能夠爲所欲爲地減速,暫停,中止或者翻轉動畫進而操做目標元素。因爲能夠適當地封裝動畫行爲,因此當在構建複雜面向對象的應用程序的時候會特別有用。

Easing 定義

天然平滑地移動會讓網絡應用擁有更好的用戶交互體驗。

天然條件下,沒有事物能夠直線地從一個點運動到另外一個點。現實生活中,在咱們周圍的物理世界中物體在移動的時候會加速或減速,由於咱們並不生活在真空狀態下且有不一樣的因素來影響事物的運行狀態。人類的大腦會指望感覺這樣的移動,因此當爲網絡應用製做動畫的時候,利用此類知識會對本身會有好處。

這是你所應該理解的術語:

  • 『ease in』-開始移動緩慢然後加速
  • 『ease out』-開始移動迅速然後減速

能夠合併兩個動畫,好比 『ease in out』。

Easing 可使得動畫更加天然平滑。

Easing 關鍵字

能夠爲 CSS 過渡和動畫選擇任意的 easing 方法。不一樣的關鍵字會影響動畫的 easing。你也能夠徹底自定義 easing 方法。

如下爲能夠選擇用來控制 easing 的 CSS 關鍵字:

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

讓咱們深刻了解並查看他們的效果。

Linear 動畫

不使用任何的 easing 方法的動畫即爲 linear

如下爲 linear 過渡效果的圖示:

值隨着時間流逝,值等比增長。使用 linear 動效,會讓動畫不天然。通常來講,避免使用 linear 動效。

使用以下代碼實現一個簡單的線性動畫:

transition: transform 500ms linear;

Ease-out 動畫

正如前所述,和 linear 對比,easing out 讓動畫快速啓動,結束時會減速。如下爲圖示:

總之,easing out 是最適合作界面體驗的,由於快速地啓動會給人以快速響應的動畫的感受,而結束時讓人感受很平滑這得歸功於不一致的移動速度。

打個比喻,好比那些跑車,首先啓動速度至關的快,這就給人以愉悅的感受。這個就比較符合人類對於動畫的感知。

有不少的方法來實現 ease out 動畫效果,而最簡單的即爲 CSS 中的 ease-out 關鍵字。

transition: transform 500ms ease-out;

Ease-in 動畫

和 ease-out 動畫相反-其啓動慢而後結束時變快。圖示以下:

和 ease-out 動畫比較,因爲他們啓動緩慢給人以反應卡頓的感受,因此 ease-in 讓人感受動畫不天然。動畫結束時很快給人一種奇怪的感受,由於整個動畫一直在加速,而現實世界中當事物忽然中止運動的時候會減速而不是加速。

和 ease-out 和 linear 動畫相似,使用 CSS 關鍵字來實現 ease-in 動畫:

transition: transform 500ms ease-in;

Ease-in-out 動畫

該動畫爲 ease-in 和 ease-out 的合集。圖示以下:

不要設置動畫持續時間過長,不然會給人一種界面不響應的感受。

使用 ease-in-out CSS 關鍵字來實現 ease-in-out 動畫:

transition: transform 500ms ease-in-out;

自定義 easing

你能夠自定義本身的 easing 曲線,這樣就更有效地控制項目中的動畫。

實際上, ease-inlinearease 關鍵字映射到預約義貝塞爾曲線 ,能夠在 CSS transitions specificationWeb Animations specification 中查找更多關於貝塞爾曲線的內容。

貝塞爾曲線

讓咱們看一下貝塞爾曲線的運行原理。一條貝塞爾曲線包含四個點,或者準確地說是包含兩組數值。每一對數值內包含表示三次貝塞爾曲線控制點的 X 和 Y 座標。貝塞爾曲線的起點座標爲 (0, 0) ,終點座標爲 (1, 1)。能夠設置兩組數值對。每一個控制點的 X 軸值必須在 [0, 1] 之間,而 Y 軸值能夠超過 [0, 1],雖然規範並無明確容許超過的數值。即便每一個控制點的 X 和 Y 值的微小差別都會輸出徹底不一樣的貝塞爾曲線。

查看維基百科關於貝塞爾曲線的說明,通俗一點講即,如今所說的即三次貝塞爾曲線,該曲線由四個點組成,P0, P1, P2, P3 組成,那麼,P0 和 P1 組成一對,P2 和 P3 組成一對,P1 和 P2 即爲控制點,P0 和 P3 即爲起始和結束節點。以下圖所示:

看下兩張擁有相近但不一樣座標的控制結點的貝塞爾曲線圖。

如你所見,兩張圖有很大不一樣。第一個控制點矢量差別爲 (0.045, 0.183),而第二個控制點矢量差別爲 (-0.427, -0.054)。

第二條曲線的樣式爲:

transition: transform 500ms cubic-bezier(0.465, 0.183, 0.153, 0.946);

第一組數值爲起始控制點的 X 和 Y 座標而第二組數值爲第二個控制點的 X 和 Y 座標。

性能優化

你得維持動畫幀數爲 60 幀每秒,不然會影響到用戶體驗。

和世界上其它事物同樣,動畫會有性能開銷。一些屬性的動畫性能開銷相比其它屬性要小。好比,爲元素的 widthheight 作動畫會更改其幾何結構而且可能會形成頁面上的其它元素移動或者大小的改變。這一過程被稱爲佈局。以前的文章中有詳細介紹過佈局和渲染。

總之,應該儘可能避免爲會引發佈局和繪製的屬性作動畫。對於大多數現代瀏覽器而言,即把動畫侷限於 opacitytransform 屬性。

Will-change

可使用 will-change 來通知瀏覽器將會更改某個元素的屬性。這會容許瀏覽器當更改某個元素屬性的時候,事先進行最恰當的優化。但不要濫用 will-change,由於這樣作會拔苗助長,使得瀏覽器浪費更多的資源,從而形成更多的性能問題。

爲 transforms 和 opacity 添加 will-change 代碼以下:

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

該屬性在 Chrome, Firefox,Opera 獲得很好的兼容。

如何選擇 JavaScript 和 CSS 來執行動畫

這個問題是無解的。只需謹記如下原則:

  • 基於 CSS 的動畫和原生支持的網頁動畫通常都是由被稱爲『合成線程』的線程來處理的。這不一樣於瀏覽器的主線程,主線程是用來執行計算樣式,佈局,繪製及 JavaScript 代碼的。這即意味着若是瀏覽器在主線程上運行耗時的任務,不會中斷動畫的運行。
  • 不少時候,也能夠由合成線程來處理 transformsopacity 屬性值的更改。
  • 若是有任何動畫觸發繪製,佈局或者同時觸發二者,『主線程』將不得不來進行處理。事實是基於 CSS 和 JavaScript 的動畫和佈局或者繪製的性能開銷將頗有可能會阻塞全部和 CSS 或者 JavaScript 運行相關的工做,從而使得渲染問題變得毫無心義。

正確使用動畫

良好的動畫爲項目添加一層使人愉快和互動的用戶體驗。你能夠隨意使用動畫,無論是寬度,調試,定位,顏色或背景色,但必須注意潛在的性能瓶頸。糟糕的動畫選擇會影響用戶體驗,因此動畫必須是高效和適當的。儘量減小使用動畫。只使用動畫來讓用戶體驗流暢天然而不是濫用。

使用動畫進行交互

不要由於只是爲了用而去使用動畫。相反,有策略性地使用動畫來增強用戶交互體驗。避免使用沒必要要的動畫來打斷或者阻礙用戶的使用。

避免爲性能開銷大的屬性作動畫

比糟糕的動畫使用更糟的是那些會引發頁面卡頓的動畫。這類動畫讓用戶感到懊喪和不快。

引用資源

疑問

能夠看下網上的這篇介紹貝塞爾曲線的文章,那麼能夠如何使用貝塞爾曲線來作出使人驚歎的動畫呢?

招賢納士

今日頭條招人啦!發送簡歷到 likun.liyuk@bytedance.com ,便可走快速內推通道,長期有效!國際化PGC部門的JD以下:c.xiumi.us/board/v5/2H…,也可內推其餘部門!

本系列持續更新中,Github 地址請查閱這裏

相關文章
相關標籤/搜索