無線頁面動畫優化實例


無線頁面本就分秒必爭,更不用說當我們在無線頁面中使用動畫的時候。不管是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 來實現相同的效果:

1 transition: left 2s ease-in-out;  ---> transition: transform 2s ease-in-out;  
2 left: xxx; ---> transform: translate3d(xxx, yyy, zzz); 

原因在於:

  • 簡單的說頁面的繪制並不是在單層的畫面里完成的,這其中有渲染層合成層等概念。對 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,利用這一個特征就可以做到函數節流,避免高頻事件在一幀內做多余的無用功的函數執行,例:
  •  1 var $box = $('#J_Test'),
     2       $point = $box.find('b');
     3 $box.on('mouseenter',function(e){
     4   requestAnimationFrame(function(){
     5       $point.css({
     6           top : e.pageY,
     7           left : e.pageX
     8       })
     9   });
    11 });
  • 分幀初始化:同樣利用一幀的執行時間將模塊的初始化或渲染函數分散到不同的幀中來執行,這樣每個模塊都有16.67ms的執行時間,而不是一股腦的堆在那里等着執行;
 1  var rAF = window.requestAnimationFrame ||  window.webkitRequestAnimationFrame || 
 2         function(c) {
 3             setTimeout(c, 1 / 60 * 1000);
 4         };
 5 
 6     function render() {
 7        self.$container.html(itemHtml);
 8        self.$container.find('.J_LazyLoad').lazyload();
 9     }
10 
11     rAF(render);

 

五、分析你的無線頁面

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

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

1 - 確定圓弧的起始弧度(0.75PI)和終止弧度(根據當前分值占上限分值的比例計算,最大為2.25PI); 
2 - 隨着時間的增長逐幀繪制終點位置 requestAnimationFrame(draw);
3 - 根據每一幀的終點位置的 cos 和 sin 值得到跟隨的圓圈坐標並繪制;

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

 

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

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

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

 


免責聲明!

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



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