無線頁面動畫優化例項

發表於2016-04-20

無線頁面本就分秒必爭,更不用說當我們在無線頁面中使用動畫的時候。不管是css動畫還是canvas動畫,我們都需要時刻小心著,並且有必要掌握頁面效能的基本分析方法。

既然我們的目標是優化,那麼就與瀏覽器的一些渲染和執行機制有關,更好的迎合瀏覽器的行為方式,才可以讓我們的動畫流暢而優美。

沒錯,瀏覽器是老大,全聽它的。

 

一、裝置重新整理率(幀率)

我們想讓頁面變快,想讓動畫流暢,我們需要先了解一下是什麼在影響著我們的感知。

頁面執行在裝置的瀏覽器中,現在市面上的移動裝置的重新整理頻率大多是60次/秒(幀率)。所以給瀏覽器渲染每一幀的畫面的時間應該是(1s/60=16.67ms)。

但實際上,瀏覽器並不是把功夫全花在為我們渲染頁面上,他還需要做一些額外的工作,比如渲染佇列的管理和不同執行緒的切換等等。所以,單純的瀏覽器渲染工作留給我們的時間大約也就是10ms左右,當我們在每一幀所做的渲染操作大於這個時間的時候,比較直觀的表現就是頁面卡頓,動畫卡頓。

當我們使用css animation完成動畫時,這一點看起來沒有那麼重要,因為瀏覽器會為我們handle一些事情。但是當我們需要使用js比如canvas來實現流暢的逐幀動畫時,需要牢記這個有限的時間,它很重要。

二、瀏覽器的頁面渲染流水線

我們的程式碼是如何一步步的渲染成頁面的呢?

  • JavaScript。一般來說,我們使用JavaScript來實現一些頁面邏輯,但偶爾我們也可能會使用JavaScript來實現一些視覺變化的效果。比如用jQuery的animate函式做一個動畫、或者往頁面裡新增一些DOM元素等。當然,現在更可能的是使用CSS Animations, Transitions和Web Animation API。
  • 計算樣式(Style)。這個過程是通過樣式檔案中的CSS選擇器,對每個DOM元素匹配對應的CSS樣式。
  • 佈局(Layout)。上一步確定了每個DOM元素的樣式規則,這一步就是具體計算每個DOM元素最終在螢幕上顯示的大小和位置。web頁面中元素的佈局是相對的,因此一個元素的佈局發生變化,會聯動地引發其他元素的佈局發生變化。因此對於瀏覽器來說,佈局過程是經常發生的。
  • 繪製(Paint)。繪製,本質上就是填充畫素的過程。包括繪製文字、顏色、影象、邊框和陰影等,也就是一個DOM元素所有的可視效果。一般來說,這個繪製過程是在多個層上完成的。
  • 渲染層合併(Composite)。由上一步可知,對頁面中DOM元素的繪製是在多個層上進行的。在每個層上完成繪製過程之後,瀏覽器會將所有層按照合理的順序合併成一個圖層,然後顯示在螢幕上。對於有位置重疊的元素的頁面,這個過程尤其重要,因為一旦圖層的合併順序出錯,將會導致元素顯示異常。

看起來每個頁面都會經歷這樣的幾個過程,然而我們其實可以使用一些技巧,幫助瀏覽器跳過某些步驟,而縮短他的工作時間。

1.五個步驟都消耗了時間

當我們在js中改變了某個DOM元素的layout時,那麼瀏覽器就會檢查頁面中的哪些元素需要重新佈局,然後對頁面激發一個reflow過程以完成頁面的重新佈局。被reflow的元素,接下來就一定會再次經過Paint和Composite這兩個過程,以渲染出最新的頁面。

 

2.跳過layout這一步

當我們只修改了一個DOM元素的paint only屬性的時候,比如background-image/color/box-shadow等。這個時候不會觸發layout,瀏覽器在完成樣式的計算之後就會跳過layout的過程,就只Paint和Composite了。

 

3.跳過layout和paint這兩步

如果你修改一個非樣式且非繪製的CSS屬性,那麼瀏覽器會在完成樣式計算之後,跳過佈局和繪製的過程,直接Composite。

我們嘗試下使用transform動畫來儘可能的達到這種效果。

 

三、使用transform實現動畫

我們可能經常需要做一些動畫,比如在做某些揭祕或者新手引導的效果時,會需要做一些將內容移入移出的操作。

當然可能第一個想到的就是 css transition 只要過渡一下 left 值或者 bottom 的值就可以了。效果或許很快就會實現,但是當我們在一個頁面頻繁的做著這樣的移入移出操作時,細心地我們放在手機中(6P)看一看,動畫並不會很流暢,尤其是在某些低端機型上。

我們換用 transform 來實現相同的效果:

原因在於:

  • 簡單的說頁面的繪製並不是在單層的畫面裡完成的,這其中有渲染層合成層等概念。對 opacity 和 transform 應用了 CSS 動畫的渲染層、有 3D 或者 perspective transform 的 CSS 屬性的渲染層等滿足一些條件的渲染層被稱為合成層;
  • 合成層有自己的渲染上下文,並且交由 GPU 處理,比 CPU 要快;
  • 當頁面需要重繪時,合成層的元素只會重繪自己層內的元素,而非整個頁面;

優化過後再放在裝置裡檢視,可以感受到效果明顯的提升。其實這裡就做到了上面提到的,節省了layout和paint。

四、從css到canvas,使用requestAnimationFrame

現在css的動畫越來越好用,也能滿足越來越多的需求。但在某些複雜的需求中我們可能還是要求助於js。

比如說我這裡實現的一個半圓的動畫:[半圓progress] [Source Code]。看起來使用css動畫就完全可以滿足我的需求,但是當需求變化的時候,我們也只能擁抱變化了。

 

**使用requestAnimationFrame**

[圓弧progress][Source Code] 這裡用canvas實現了自定義弧度圓弧的增長動畫。

這裡我們藉助這個動畫效果看一下是如何使用canvas和requestAnimationFrame來實現流暢的逐幀動畫的。

window.requestAnimationFrame 是一個專門為動畫而生的 web API 。它通知瀏覽器在頁面重繪前執行你的回撥函式。通常來說被呼叫的頻率是每秒60次。

假設我們的頁面上有一個動畫效果,如果我們想保證每一幀的順利繪製,那麼我們就需要requestAnimationFrame來保證我們的繪製時機了。

很多框架和示例程式碼都是用setTimeoutsetInterval來實現頁面中的動畫效果,比如jQuery中的animation。這種實現方式的問題是,你在setTimeoutsetInterval中指定的回撥函式的執行時機是無法保證的。它將在這一幀動畫的_某個時間點_被執行,很可能是在幀結束的時候。這就意味這我們可能失去這一幀的資訊。

 

**requestAnimationFrame的其他高能用法**

根據requestAnimationFrame的特性,其實我們還可以在很多別的想不到的地方來一顯身手。

  • 動畫:也是它的主要用途,它將我們動畫的執行時機和執行頻率交由瀏覽器決定,以得到更好的效能;
  • 函式節流:requestAnimationFrame 的執行頻率(一幀)是16.67ms,利用這一個特徵就可以做到函式節流,避免高頻事件在一幀內做多餘的無用功的函式執行,例:
  • 分幀初始化:同樣利用一幀的執行時間將模組的初始化或渲染函式分散到不同的幀中來執行,這樣每個模組都有16.67ms的執行時間,而不是一股腦的堆在那裡等著執行;

五、分析你的無線頁面

我們還是藉助這個例子,[圓弧progress][Source Code] 簡單的看下如何分析無線頁面的效能。

這裡的實現思路是這樣的:

但當然,實現完成只是走了第一步,我們來藉助Chrome Timeline來分析一下這個簡單的頁面。

 

  1. 看一下幀率,在進度動畫進行的時候,看起來幀率不錯,沒有產生掉幀的現象,說明每一幀的耗時都還ok,我的動畫基本不會卡頓;
  2. 在函式的執行和呼叫那一欄中,可能有問題的部分右上角會被標紅,還可以檢視可能存在問題的細節;這裡提示我頁面強制重排了,仔細觀察下面的 Bottom-up tab 中可以定位到具體的程式碼。

使用Timeline就可以看到頁面的幾種指標,幀率,js執行等等。就可以針對出現問題的幀下手優化。

在分析頁面效能的時候,嚴重推薦閱讀:[https://developer.chrome.com/devtools/docs/timeline] .timeline的詳細使用說明,它真的很強大,能幫助我們分析到頁面的各個方面的問題。

相關文章