高性能移動端開發

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

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

 

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

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

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

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

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

 

一. 瀏覽器渲染原理介紹

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

這就是上圖中的<16ms。瀏覽器在一幀裏面,會作如下這些動做。 固然,有些步驟(好比 layout,paint)是能夠省略的。閉包

若是改變屬性在上面圖中越往左,那麼影響就越大,效率就越低。dom

瀏覽器渲染的流程以下:ide

  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

相關文章
相關標籤/搜索