H5動畫優化之路


H5動畫60fps之路

在移動端,和Native相比,H5一直都被人吐槽性能差,尤其是在動畫方面。
談到整個Web app的生命周期,一般分為四個部分:

  1. 加載
  2. 等待用戶
  3. 響應用戶
  4. 動畫

一般情況下,首屏加載的時間應該小於1s,而響應用戶行為的時間應該小於100ms,動畫應該達到60fps。這篇文章只針對動畫60fps的優化。

關鍵渲染路徑

動畫性能高,從直觀上來看是動畫沒有抖動和卡頓,從數字上是渲染達到了60fps。60fps就是每秒60幀,所以每幀的時間只有1000ms/60=16.67ms。實際上,瀏覽器在每一幀還要做一些額外的事情,所以如果要達成60fps,我們需要保證每一幀的時間在10ms到12ms之間。

我們先來看看瀏覽器在每一幀渲染都做了哪些事情。

javascript

現階段CSS動畫接口比較有限,許多復雜的動畫如果css不能完成,就需要使用requestAnimationFrame函數,這樣每幀都會有JavaScript的計算。

Style

當一個新的樣式應用到Dom上的時候,就會引起Style的計算,同JavaScript,如果開發者使用CSS Animation,CSS Transition,那么瀏覽器只有在動畫開始之前會做Sytle的計算,而requireAnimationFrame會在每幀都計算。

Layout

當新的CSS樣式出發了Layout,比如修改了width,height,pisition,這時瀏覽器需要重新Layout受到影響的元素,大部分情況下,即使一個位置很遠的元素發生很小的寬度改變,也會引起整個document的layout,這在動畫里是一個性能非常低的實現,應該盡量避免。

Paint

Layout變化后,受到影響的元素會重新Paint,如果遇到首次加載圖片,瀏覽器需要將圖片先解碼放入內存(Image Decode)。Painting是在多個Surface上進行的,最后會出來多個Layer。

Composite

最后一步Composite就是把已經Paint好的各個Layer合成到一起。瀏覽器會將每個層分成多個Tiles去Paint,但是這些作為H5開發者來說是不能夠控制的,這些層和Tiles信息會被傳到GPU,最終由GPU負責渲染並顯示在屏幕上。

三條路

並不是每個CSS的變動引起的渲染過程都要經歷以上全部,實際有三條路可以走。
第一條路,比如修改元素的width,這些步驟都會執行。
第二條路:修改元素的background-color:不會有布局的變化,不會觸發Layout,但是會發生Paint
第三條路:修改元素的transform,不會觸發Layout,也不會觸發Paint
當我們要完成一個動畫時,可能有很多種實現的方式,比如讓一個小球向右平移100px,我們可以用以下這些方法:

.move{left:100px};
.move{margin-left:100px};
.move{padding-left:100px};
.move{transform:translateX(100px)};

問題就在這里,使用哪種方式性能最高?
從上面看出,如果使用不觸發Layout和Paint的CSS屬性完成動畫,性能是最高的。那么哪些CSS屬性不會觸發
Layout和Paint呢?
推薦一個網站CSS TRIGGERS,這里詳細的列出了所有CSS屬性在修改后是否觸發Layout和Paint,是個非常給力的工具!

使用Chrome的DevTools調試

調試動畫就要用到timeline,這里列出3個比較實用的功能。

  1. 查看每幀的時間:通過錄制跟蹤一段動畫,可以看到每一幀的情況,找到耗時超過16ms的幀可以有針對性地解決問題。
  2. 查看內存:通過Memory工具可以查看內存的使用情況,對於內存泄露的檢查是非常有幫助的。
  3. 查看CompositeLayer:通過Layer可以查看當前繪制的幀有多少需要Composite的Layer,Layer越多,帶來的Upate LayerTree和Composite Layer的時間就越長。

優化方法

使用requestAnimationFrame

前面提到,如果動畫性能達到60fps,那么每一幀的時間是16ms,瀏覽器會有一些額外的工作,所以要保證所有的事情在10ms到12ms完成,那么留給JavaScript的時間在3ms到4ms比較合適。

大多數比較早期的Web動畫是用setTimeout/setInterval這兩個函數實現的,包括著名的JQuery,那個年代也沒有其它的API可以用。這種實現方式性能非常低效,因為JavaScript在處理setTimeout/setInterval時,不會和渲染流程聯系起來,很有可能在一幀的中間突然執行JavaScript導致Sytle和Layout等后面的處理要重新進行而不能在16ms內完成全部。

但是現在不同了,我們有了requestAnimationFrame,使用這個函數,動畫每一幀在何時執行,如何執行,都由瀏覽器去決定,開發者只要把每次執行的工作寫好就可以了。

    function animate(){
        //do something change here,like:
        //x+=0.1;element.style.opacity=x;
        requestAnimationFrame(animate);
    }
    requestAnimationFrame(animate);

查看耗時較長的js,必要時使用WebWorker

如果在動畫執行過程中,有額外的大量JavaScript計算,為了不影響動畫,建議將大量計算的JavaScript放入WebWorker。WebWorker是個功能有限的實現JavaScript多線程的工具,Worker和主線程通過message事件和postMessage方法通訊,API可以參考WebWorker。

micro Optimization

V8引擎已經可以幫助我們處理這些細小的優化。但是作為一種良好的編碼習慣,還是要保持。

Style優化

瀏覽器花多長時間處理Style的計算取決於有多少元素受到了影響,所以改變1000個元素的Style所耗費的時間,是改變10個元素的100倍。有了這個結論,我們在做每個動畫時,要確保每次改變CSS時,所影響的元素都是我們需要改變的,不要改變和動畫無關的元素。

避免Layput Thrashing

什么是 Layout Thrashing?先看一個例子:

for(var i=0;i<elements.length;i++){
    var blockWidth=baseElement.Width;
    elements[i].style.width=blockWidth;
}

這是一個性能很低的例子。
當開發者設置了一個CSS樣式,瀏覽器會繼續等待看有沒有更多的樣式改變,然后在接下來的一幀來個批處理。
我們再看上面那個性能低的例子,設置好一個新的樣式,接着讓瀏覽器去讀一個Layout,這樣瀏覽器就不能等待多個樣式的批處理,而是強制讓它先把剛才的樣式做渲染,這樣一讀一寫地循環下去,性能是非常低的,叫做“Forced Synchonous Layout”。

這個例子修改起來非常簡單,只要把讀Layout的事情放在循環外就好了。

var blockWidth=baseElement.Width;
for(var i=0;i<elements.length;i++){
    elements[i].style.width=blockWidth;
}

Painting和Composite優化

Painting是非常耗性能的,所以為了避免Painting,我們一般會把這個元素變成一個Layer,當它成為一個單獨Layer后,就會獨立出來,當在它后面的其他元素需要重繪時,它也不受到影響,只是最后需要做Composite合成。但是並不是Layer越多越好,這樣會加重Composite的工作,所以開發者需要在Paint和Composite上找到一個平衡點。

如何讓一個元素變成Layer呢?目前有幾種Hack方式:

.layer{transform:translate3d(0,0,0)}
.layer{transform:translateZ(0)}

Chrome提供了一種更好的方式:使用will-change,和上面不同,will-change不會直接把元素變為Layer,而是給瀏覽器一個提示,這可以讓瀏覽器自己決定是否成為Layer,推薦這種方式:

.layer{will-change:transform;}

推薦一篇Chrome官方的文章GPU Accelerated Compositing in Chrome,這里面非常詳細地講述了從RenderObject到RenderLayer,到GraphicsLayer,最后變成Chrome Compositor Layers的一系列過程,非常詳細,文章里提到的GraphicsLayer就是本文的Layer。

上面分別從JavaScript, Style, Paint的過程介紹了一些針對性的優化方法,通過使用Chrome的DevTools,開發者可以針對渲染路徑上的每一個步驟優化,相信一定會有非常好的效果。
我將這些優化點簡單總結為一個圖:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM