高性能移動端開發


不知不覺,春節就過完了,還沒來得及好好享受就沒了。好想來一場說走就走的旅行✈️,不吹水了,直接進入正題。

最近在做一個需求,發現了薄弱的地方,趁這個好機會深入了解一下,拓寬一下視野~

 

眾所周知,網頁不僅應該被快速加載,同時還應該流暢運行,比如快速響應的交互,如絲般順滑的動畫……

在實際開發中如何做到上面所說的效果呢?

1. 確認渲染性能的分析標准
2. 准備尺子去衡量渲染性能標准
3. 對耗時多的地方進行優化
 
 
我們可以粗略的得到下面的優化目標

第一個是 首屏呈現時間,網上的資料已經非常非常多了,壓縮代碼,使用webp圖片,使用sprite,按需加載,“直出”,CDN…… 

第二個是 16ms 優化,本篇重點講16ms的優化。

 

一. 瀏覽器渲染原理介紹

大多數設備的刷新頻率是60次/秒,(1000/60 = 16.6ms)也就說是瀏覽器對每一幀畫面的渲染工作要在16ms內完成,超出這個時間,頁面的渲染就會出現卡頓現象,影響用戶體驗。

這就是上圖中的<16ms。瀏覽器在一幀里面,會做以下這些動作。 當然,有些步驟(比如 layout,paint)是可以省略的。

如果改變屬性在上面圖中越往左,那么影響就越大,效率就越低。

瀏覽器渲染的流程如下:

  1. 獲取 DOM 並將其分割為多個層(RenderLayer)
  2. 將每個層柵格化,並獨立的繪制進位圖中
  3. 將這些位圖作為紋理上傳至 GPU
  4. 復合多個層來生成最終的屏幕圖像(終極layer)。

從上面圖中可以看出,如果只是改變composite(渲染層合並),那效率就會大大提高。

下面粗略地列出改變哪些樣式會分別改變渲染過程的哪一模塊。

從上圖可以看到 transform,opacity 只會改變composite(渲染層合並),為什么呢?因為開啟了GPU加速。

 

開啟 GPU 加速

字面上的解釋: 紋理能夠以很低的代價 映射到不同的位置,而且還能夠以很低的代價通過把它們應用到一個非常簡單的矩形網格中進行變形。
【字面上的理解非常地繞口,還是老道理,能用圖講清的道理不要用文字。】

小tips:先選中timeline的某一幀,然后選擇下面的layer標簽tab,可以左右拖動該模塊出現3d

我們可以看到頁面上由如下層組成:

 

雖然我們最終在瀏覽器上看到的只是一個復印版,即最終只有一個層。類似於PhotoShop軟件中的“圖層”概念,最后合並所有可視圖層,輸出一張圖片到屏幕上

但是實際上一個頁面會因為一些規則被分成相應的層一旦被獨立出來之后,便不會再影響其他dom的布局,因為它改變之后,只是“貼上”了頁面。

 

目前下面這些因素都會引起Chrome創建層:
  • 3D 或透視變換(perspective transform) CSS 屬性
  • 使用加速視頻解碼的 <video> 元素
  • 擁有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 混合插件(如 Flash)
  • 對自己的 opacity 做 CSS 動畫或使用一個動畫 webkit 變換的元素
  • 擁有加速 CSS 過濾器的元素
  • 元素有一個包含復合層的后代節點(換句話說,就是一個元素擁有一個子元素,該子元素在自己的層里)
  • 元素有一個 z-index 較低且包含一個復合層的兄弟元素(換句話說就是該元素在復合層上面渲染)
  • 在webkit內核的瀏覽器中,如果有上述情況,則會創建一個獨立的layer。

  

需要注意的是,不要創建過多的渲染層,這意味着新的內存分配和更復雜的層管理。不要濫用GPU加速,注意看 composite layouts 是否超出了 16ms

 

 

說了這么多瀏覽器渲染的原理,如果沒有尺子測量也毫無用處。那么,下面就選尺子去丈量:谷歌開發工具的Timeline。

二. 谷歌開發工具 Timeline 的常用功能

1. 點擊左上角的錄制之后,錄制結束后會生成下面的樣子,紅色區域內就是幀了,移動上去可以看到每一幀的頻率,如果>60fps,就是比較流暢,如果<60fps,就會感到卡頓

 

2. 在timeline下面,可以看到各個模塊的耗時,可以定位到耗時較大的函數上面,對該函數進行優化。

 
 
3. 按照下面步驟選擇,即 可看到獨立的層,高亮重繪的區域,方便找出不必要重繪的區域,進行優化

選擇之后,當前頁面會出現下面2中顏色邊框
黃色邊框: 有動畫3d變換的元素,表示放到了一個新的復合層(composited layer)中渲染
藍色的柵格:這些分塊可以看作是比層更低一級的單位,Chrome以這些分塊為單位,一次向GPU上傳一個分塊的內容。
 
 

工具也有了,瀏覽器渲染的原理也知道了,接下來是結合實際項目進行優化.

三. 在實際項目中進行 16.6ms 優化

結合上面的渲染流程圖,我們可以針對性的分析並優化下面的一些步驟

  • 優化JavaScript的執行效率
  • 降低樣式計算的范圍和復雜度
  • 避免大規模、復雜的布局
  • 簡化繪制的復雜度、減少繪制區域
  • 優先使用渲染層合並屬性、控制層數量
  • 對用戶輸入事件的處理函數去抖動(移動設備) 

 

1. 讀寫分離,批量操作 

JavaScript腳本運行的時候,它能獲取到的元素樣式屬性值都是上一幀畫面的,都是舊的值。

因此,如果你在當前幀獲取屬性之前又對元素節點有改動,那就會導致瀏覽器必須先應用屬性修改,結果執行布局過程,最后再執行JavaScript邏輯。

// 先寫后讀,觸發強制布局
function logBoxHeight() {
    // 更新box樣式
    box.classList.add('super-big'); // 為了返回box的offersetHeight值 // 瀏覽器必須先應用屬性修改,接着執行布局過程  console.log(box.offsetHeight); }
優化之后:
// 先讀后寫,避免強制布局
function logBoxHeight() {
    // 獲取box.offsetHeight
    console.log(box.offsetHeight);

    // 更新box樣式
    box.classList.add('super-big');
}

 

2. 閉包緩存計算結果   (需要頻繁的調用,計算的函數)
 1 getMaxWidth: (function () {
 2             var cache = {}; 3 function getwidth() { 4 if (maxWidth in cache) { 5 return cache[maxWidth]; 6  } 7 var target = this.node, 8 width = this.width, 9 screen = document.body.clientWidth, 10 num = target.length, 11 maxWidth = num * width + 10 * num + 20 - screen; 12 cache[maxWidth] = maxWidth; 13 return maxWidth; 14  } 15 return getwidth; 16 })(),

改成這種方式后,直接蹭蹭蹭~ 減少了10多ms

 
3. 對用戶輸入事件的處理函數去抖動
如果被觸摸的元素綁定了輸入事件處理函數,比如touchstart/touchmove/touchend,那么渲染層合並線程必須等待這些被綁定的處理函數執行完畢才能執行,也就是用戶的滾動頁面操作被阻塞了,表現出的行為就是滾動出現延遲或者卡頓。
簡而言之就是你必須確保用戶輸入 事件綁定的任何處理函數都能夠快速的執行完畢,以便騰出時間來讓渲染層合並線程完成他的工作
輸入事件處理函數,比如scroll/touch事件的處理,都會在requestAnimationFrame之前被調用執行。因此,如果你在上述輸入事件的處理函數中做了修改樣式屬性的操作,那么這些操作就會被瀏覽器暫存起來。

然后在調用requestAnimationFrame的時候,如果你在一開始就做了讀取樣式屬性的操作,那么將會觸發瀏覽器的強制同步布局操作(即在javascript階段中執行布局),這樣會導致多次布局,效率低下。

 

優化如下:

window.requestAnimationFrame(function () {
    context.animateTo(nowPos);  //需要更新位置的交給RAF
});
 
4. 減少不必要的重繪

續上面,開啟paint flashing 之后,可以看到瀏覽器重新繪制了哪些區域。發現有一些不必要重繪的區域也重繪了~給這些開啟GPU優化(上文中提到)

直接看 timeline 效果,全綠了~懸着的心終於放下了

 

 

 

參考文章: http://www.jianshu.com/p/a32b890c29b1

 


免責聲明!

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



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