盒子端 CSS 動畫性能提高研究

不一樣於傳統的 PC Web 或者是移動 WEB,在騰訊視頻客廳盒子端,接大屏顯示器(電視)下,許多能流暢運行於 PC 端、移動端的 Web 動畫,受限於硬件水平,在盒子端的表現的每每不盡如人意。javascript

基於此,對於 Web 動畫的性能問題,僅僅停留在感受已經優化的OK之上,是不夠的,想要在盒子端跑出高性能接近 60 FPS 的流暢動畫,就必需要刨根問底,深挖每一處能夠提高的方法。css

 

流暢動畫的標準

理論上說,FPS 越高,動畫會越流暢,目前大多數設備的屏幕刷新率爲 60 次/秒,因此一般來說 FPS 爲 60frame/s 時動畫效果最好,也就是每幀的消耗時間爲 16.67ms。html

直觀感覺,不一樣幀率的體驗

  • 幀率可以達到 50 ~ 60 FPS 的動畫將會至關流暢,讓人倍感溫馨;
  • 幀率在 30 ~ 50 FPS 之間的動畫,因各人敏感程度不一樣,溫馨度因人而異;
  • 幀率在 30 FPS 如下的動畫,讓人感受到明顯的卡頓和不適感;
  • 幀率波動很大的動畫,亦會令人感受到卡頓。

 

盒子端動畫優化

在騰訊視頻客廳盒子端,Web 動畫未進行優化以前,一些複雜動畫的幀率僅有 10 ~ 30 FPS,卡頓感很是明顯,帶來很很差的用戶體驗。java

而進行優化以後,能將 10 ~ 30 FPS的動畫優化至 30 ~ 60 FPS,雖然不算優化到最完美,可是當前盒子硬件的條件下,已經算是很是大的進步。web

盒子端 Web 動畫性能比較

首先先給出在盒子端不一樣類型的Web 動畫的性能比較。通過對比,在盒子端 CSS 動畫的性能要優於 Javascript 動畫,而在 CSS 動畫裏,使用 GPU 硬件加速的動畫性能要優於不使用硬件加速的性能。chrome

因此在盒子端,實現一個 Web 動畫,優先級是:瀏覽器

GPU 硬件加速 CSS 動畫 > 非硬件加速 CSS 動畫 > Javascript 動畫less

 

動畫性能上報分析

要有優化,就必須得有數據作爲支撐。對比優化先後是否有提高。而對於動畫而言,衡量一個動畫的標準也就是 FPS 值。dom

因此如今的關鍵是如何計算出每一個動畫運行時的幀率,這裏我使用的是 requestAnimationFrame這個函數近似的獲得動畫運行時的幀率。ide

考慮到盒子都是安卓系統,且大多版本較低且硬件性能堪憂,致使一是許多高級 API 沒法使用,二是這裏只是近似獲得動畫幀率

原理是,正常而言 requestAnimationFrame 這個方法在一秒內會執行 60 次,也就是不掉幀的狀況下。假設動畫在時間 A 開始執行,在時間 B 結束,耗時 x ms。而中間 requestAnimationFrame 一共執行了 n 次,則此段動畫的幀率大體爲:n / (B - A)。

核心代碼以下,能近似計算每秒頁面幀率,以及咱們額外記錄一個 allFrameCount,用於記錄 rAF 的執行次數,用於計算每次動畫的幀率 :

var rAF = function () {
    return (
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        }
    );
}();

var frame = 0;
var allFrameCount = 0;
var lastTime = Date.now();
var lastFameTime = Date.now();

var loop = function () {
    var now = Date.now();
    var fs = (now - lastFameTime);
    var fps = Math.round(1000 / fs);

    lastFameTime = now;
    // 不置 0,在動畫的開頭及結尾記錄此值的差值算出 FPS
    allFrameCount++;
    frame++;

    if (now > 1000 + lastTime) {
        var fps = Math.round((frame * 1000) / (now - lastTime));
        // console.log('fps', fps); 每秒 FPS
        frame = 0;
        lastTime = now;
    };

    rAF(loop);
}

  

研究結論

因此,咱們的目標就是在使用 GPU 硬件加速的基礎之上,更深刻的去優化 CSS 動畫,先給出最後的一個優化步驟方案:

  1. 精簡 DOM ,合理佈局
  2. 使用 transform 代替 left、top,減小使用耗性能樣式
  3. 控制頻繁動畫的層級關係
  4. 考慮使用 will-change
  5. 使用 dev-tool 時間線 timeline 觀察,找出致使高耗時、掉幀的關鍵操做

下文會有每一步驟的具體分析解釋。

 

Web 每一幀的渲染

要想達到 60 FPS,每幀的預算時間僅比 16 毫秒多一點 (1 秒/ 60 = 16.67 毫秒)。但實際上,瀏覽器有整理工做要作,所以您的全部工做須要儘可能在 10 毫秒內完成。

而每一幀,若是有必要,咱們能控制的部分,也是像素至屏幕管道中的關鍵步驟以下:

完整的像素管道 JS / CSS > 樣式 > 佈局 > 繪製 > 合成:

  1. JavaScript。通常來講,咱們會使用 JavaScript 來實現一些視覺變化的效果。好比用 jQuery 的 animate 函數作一個動畫、對一個數據集進行排序或者往頁面裏添加一些 DOM 元素等。固然,除了 JavaScript,還有其餘一些經常使用方法也能夠實現視覺變化效果,好比:CSS Animations、Transitions 和 Web Animation API。

  2. 樣式計算。此過程是根據匹配選擇器(例如 .headline 或 .nav > .nav__item)計算出哪些元素應用哪些 CSS 3. 規則的過程。從中知道規則以後,將應用規則並計算每一個元素的最終樣式。

  3. 佈局。在知道對一個元素應用哪些規則以後,瀏覽器便可開始計算它要佔據的空間大小及其在屏幕的位置。網頁的佈局模式意味着一個元素可能影響其餘元素,例如 <body> 元素的寬度通常會影響其子元素的寬度以及樹中各處的節點,所以對於瀏覽器來講,佈局過程是常常發生的。

  4. 繪製。繪製是填充像素的過程。它涉及繪出文本、顏色、圖像、邊框和陰影,基本上包括元素的每一個可視部分。繪製通常是在多個表面(一般稱爲層)上完成的。

  5. 合成。因爲頁面的各部分可能被繪製到多層,由此它們須要按正確順序繪製到屏幕上,以便正確渲染頁面。對於與另外一元素重疊的元素來講,這點特別重要,由於一個錯誤可能使一個元素錯誤地出如今另外一個元素的上層。

固然,不必定每幀都老是會通過管道每一個部分的處理。咱們的目標就是,每一幀的動畫,對於上述的管道流程,能避免則避免,不能避免則最大限度優化。

 

優化動畫步驟

先給出一個步驟,調優一個動畫,有必定的指導原則能夠遵循,一步一步深刻動畫:

1.精簡 DOM ,合理佈局

這個沒什麼好說的,若是能夠,精簡 DOM 結構在任什麼時候候都是對頁面有幫助的。

2.使用 transform 代替 left、top,減小使用耗性能樣式

現代瀏覽器在完成如下四種屬性的動畫時,消耗成本較低:

  • position(位置): transform: translate(npx, npx)
  • scale(比例縮放):transform: scale(n)
  • rotation(旋轉) :transform: rotate(ndeg)
  • opacity(透明度):opacity: 0...1

若是能夠,儘可能只使用上述四種屬性去控制動畫。

不一樣樣式在消耗性能方面是不一樣的,改變一些屬性的開銷比改變其餘屬性要多,所以更可能使動畫卡頓。

例如,與改變元素的文本顏色相比,改變元素的 box-shadow 將須要開銷大不少的繪圖操做。 改變元素的 width 可能比改變其 transform 要多一些開銷。如 box-shadow 屬性,從渲染角度來說十分耗性能,緣由就是與其餘樣式相比,它們的繪製代碼執行時間過長。

這就是說,若是一個耗性能嚴重的樣式常常須要重繪,那麼你就會遇到性能問題。其次你要知道,沒有不變的事情,在今天性能不好的樣式,可能明天就被優化,而且瀏覽器之間也存在差別。

開啓 GPU 硬件加速

歸根結底,上述四種屬性的動畫消耗較低的緣由是會開啓了 GPU 硬件加速。動畫元素生成了本身的圖形層(GraphicsLayer)。

一般而言,開啓 GPU 加速的方法咱們可使用

  • will-change: transform

這會使聲明瞭該樣式屬性的元素生成一個圖形層,告訴瀏覽器接下來該元素將會進行 transform 變換,讓瀏覽器提早作好準備。

使用 will-change 並不必定會有性能的提高,由於即便瀏覽器預料到會有這些更改,依然會爲這些屬性運行佈局和繪製流程,因此提早告訴瀏覽器,也並不會有太多性能上的提高。這樣作的好處是,建立新的圖層代價很高,而等到須要時匆忙地建立,不如一開始直接建立好。

對於 Safari 及一些舊版本瀏覽器,它們不能識別 will-change,則須要使用某種 translate 3D 進行 hack,一般會使用

  • transform: translateZ(0)

因此,正常而言,在生產環境下,咱們可能須要使用以下代碼,開啓硬件加速:

{
    will-change: transform;
    transform: translateZ(0);
}

3.控制頻繁動畫的層級關係

動畫層級的控制的意思是儘可能讓須要進行 CSS 動畫的元素的 z-index 保持在頁面最上方,避免瀏覽器建立沒必要要的圖形層(GraphicsLayer),可以很好的提高渲染性能。

OK,這裏又提到了圖形層(GraphicsLayer),這是一個瀏覽器渲染原理相關的知識(WebKit/blink內核下)。它能對動畫進行加速,但同時也存在相應的加速坑!

 

簡單來講,瀏覽器爲了提高動畫的性能,爲了在動畫的每一幀的過程當中沒必要每次都從新繪製整個頁面。在特定方式下能夠觸發生成一個合成層,合成層擁有單獨的 GraphicsLayer。

須要進行動畫的元素包含在這個合成層之下,這樣動畫的每一幀只須要去從新繪製這個 Graphics Layer 便可,從而達到提高動畫性能的目的。

那麼一個元素何時會觸發建立一個 Graphics Layer 層?從目前來講,知足如下任意狀況便會建立層:

  • 硬件加速的 iframe 元素(好比 iframe 嵌入的頁面中有合成層)
  • 硬件加速的插件,好比 flash 等等
  • 使用加速視頻解碼的 <video> 元素
  • 3D 或者 硬件加速的 2D Canvas 元素
  • 3D 或透視變換 (perspective、transform) 的 CSS 屬性
  • 對本身的 opacity 作 CSS 動畫或使用一個動畫變換的元素
  • 擁有加速 CSS 過濾器的元素
  • 元素有一個包含複合層的後代節點(換句話說,就是一個元素擁有一個子元素,該子元素在本身的層裏)
  • 元素有一個 z-index 較低且包含一個複合層的兄弟元素

本小點中說到的動畫層級的控制,緣由就在於上面生成層的最後一條:

元素有一個 z-index 較低且包含一個複合層的兄弟元素。

這裏是存在坑的地方,首先咱們要明確兩點:

  1. 咱們但願咱們的動畫獲得 GPU 硬件加速,因此咱們會利用相似 transform: translateZ()這樣的方式生成一個 Graphics Layer 層。
  2. Graphics Layer 雖好,但不是越多越好,每一幀的渲染內核都會去遍歷計算當前全部的 Graphics Layer ,並計算他們下一幀的重繪區域,因此過量的 Graphics Layer 計算也會給渲染形成性能影響。

記住這兩點以後,回到上面咱們說的坑。

假設咱們有一個輪播圖,有一個 ul 列表,結構以下:

<div class="container">
<div class="swiper">輪播圖</div>
<ul class="list">
<li>列表li</li>
<li>列表li</li>
<li>列表li</li>
<li>列表li</li>
</ul>
</div>

假設給他們定義以下 CSS:

.swiper {
    position: static;
    animation: 10s move infinite;
}

.list {
    position: relative;
}

@keyframes move {
    100% {
        transform: translate3d(10px, 0, 0);
    }
}

因爲給 .swiper 添加了 translate3d(10px, 0, 0) 動畫,因此它會生成一個 Graphics Layer,以下圖所示,用開發者工具能夠打開層的展現,圖形外的黃色邊框即表明生成了一個獨立的複合層,擁有獨立的 Graphics Layer 。

可是!在上面的圖中,咱們並無給下面的 list 也添加任何能觸發生成 Graphics Layer 的屬性,可是它也一樣也有黃色的邊框,生成了一個獨立的複合層。

緣由在於上面那條元素有一個 z-index 較低且包含一個複合層的兄弟元素。咱們並不但願 list 元素也生成 Graphics Layer ,可是因爲 CSS 層級定義緣由,下面的 list 的層級高於上面的 swiper,因此它被動的也生成了一個 Graphics Layer 。

使用 Chrome,咱們也能夠觀察到這種層級關係,能夠看到 .list 的層級高於 .swiper

 

因此,下面咱們修改一下 CSS ,改爲:

.swiper {
    position: relative;
    z-index: 100;
}

.list {
    position: relative;
}

這裏,咱們明確使得 .swiper 的層級高於 .list ,再打開開發者工具觀察一下:

 

能夠看到,這一次,.list 元素已經沒有了黃色外邊框,說明此時沒有生成 Graphics Layer 。再看看層級圖:

此時,層級關係纔是咱們但願看到的,.list 元素沒有觸發生成 Graphics Layer 。而咱們但願須要硬件加速的 .swiper 保持在最上方,每次動畫過程當中只會獨立重繪這部分的區域。

總結

這個坑最先見於張雲龍發佈的這篇文章CSS3硬件加速也有坑,這裏還要總結補充的是:

  • GPU 硬件加速也會有坑,當咱們但願使用利用相似 transform: translate3d() 這樣的方式開啓 GPU 硬件加速,必定要注意元素層級的關係,儘可能保持讓須要進行 CSS 動畫的元素的 z-index 保持在頁面最上方。

  • Graphics Layer 不是越多越好,每一幀的渲染內核都會去遍歷計算當前全部的 Graphics Layer ,並計算他們下一幀的重繪區域,因此過量的 Graphics Layer 計算也會給渲染形成性能影響。

  • 可使用 Chrome ,用上面介紹的兩個工具對本身的頁面生成的 Graphics Layer 和元素層級進行觀察而後進行相應修改。

  • 上面觀察頁面層級的 chrome 工具很是吃內存?好像仍是一個處於實驗室的功能,分析稍微大一點的頁面容易直接卡死,因此要多學會使用第一種觀察黃色邊框的方式查看頁面生成的 Graphics Layer 這種方式。

4. 使用 will-change 能夠在元素屬性真正發生變化以前提早作好對應準備

// 示例
.example {
    will-change: transform;
}

上面已經提到過 will-change 了。

will-change 爲 web 開發者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器能夠在元素屬性真正發生變化以前提早作好對應的優化準備工做。 這種優化能夠將一部分複雜的計算工做提早準備好,使頁面的反應更爲快速靈敏。

值得注意的是,用好這個屬性並非很容易:

  • 在一些低端盒子上,will-change 會致使不少小問題,譬如會使圖片模糊,有的時候很容易拔苗助長,因此使用的時候還須要多加測試。

  • 不要將 will-change 應用到太多元素上:瀏覽器已經盡力嘗試去優化一切能夠優化的東西了。有一些更強力的優化,若是與 will-change 結合在一塊兒的話,有可能會消耗不少機器資源,若是過分使用的話,可能致使頁面響應緩慢或者消耗很是多的資源。

  • 有節制地使用:一般,當元素恢復到初始狀態時,瀏覽器會丟棄掉以前作的優化工做。可是若是直接在樣式表中顯式聲明瞭 will-change 屬性,則表示目標元素可能會常常變化,瀏覽器會將優化工做保存得比以前更久。因此最佳實踐是當元素變化以前和以後經過腳原本切換 will-change 的值。

  • 不要過早應用 will-change 優化:若是你的頁面在性能方面沒什麼問題,則不要添加 will-change 屬性來榨取一丁點的速度。 will-change 的設計初衷是做爲最後的優化手段,用來嘗試解決現有的性能問題。它不該該被用來預防性能問題。過分使用 will-change 會致使生成大量圖層,進而致使大量的內存佔用,並會致使更復雜的渲染過程,由於瀏覽器會試圖準備可能存在的變化過程,這會致使更嚴重的性能問題。

  • 給它足夠的工做時間:這個屬性是用來讓頁面開發者告知瀏覽器哪些屬性可能會變化的。而後瀏覽器能夠選擇在變化發生前提早去作一些優化工做。因此給瀏覽器一點時間去真正作這些優化工做是很是重要的。使用時須要嘗試去找到一些方法提早必定時間獲知元素可能發生的變化,而後爲它加上 will-change 屬性。

5. 使用 dev-tool 時間線 timeline 觀察,找出致使高耗時、掉幀的關鍵操做

1)對比屏幕快照,觀察每一幀包含的內容及具體的操做

2)找到掉幀的那一幀,分析該幀內不一樣步驟的耗時佔比,進行有針對性的優化

3)觀察是否存在內存泄漏

 對於 timeline 的使用用法,這裏有個很是好的教程,通俗易懂,能夠看看:

瀏覽器渲染優化 Udacity 課程

 

總結一下

對於盒子端 CSS 動畫的性能,不少方面仍處於探索中,本文大量內容在以前文章已經出現過,這裏更多的是概括總結提煉成可參照執行的流程。

本文的優化方案研究一樣適用於 PC Web 及移動 Web,文章不免有錯誤及疏漏,歡迎不吝賜教。

相關文章
相關標籤/搜索