瀏覽器渲染基本原理(五):優化渲染性能

瀏覽器渲染基本原理(五):優化渲染性能css

渲染卡頓是怎麼回事?

網頁不只應該被快速加載,同時還應該流暢運行,好比快速響應的交互,如絲般順滑的動畫等。
大多數設備的刷新頻率是60次/秒,也就說是瀏覽器對每一幀畫面的渲染工做要在16ms內完成,超出這個時間,頁面的渲染就會出現卡頓現象,影響用戶體驗。
爲了保證頁面的渲染效果,須要充分了解瀏覽器是如何處理HTML/JavaScript/CSS的。jquery

渲染流程分爲幾步?

 

JavaScript:JavaScript實現動畫效果,DOM元素操做等。
Style(計算樣式):肯定每一個DOM元素應該應用什麼CSS規則。
Layout(佈局):計算每一個DOM元素在最終屏幕上顯示的大小和位置。因爲web頁面的元素佈局是相對的,因此其中任意一個元素的位置發生變化,都會聯動的引發其餘元素髮生變化,這個過程叫reflow。
Paint(繪製):在多個層上繪製DOM元素的的文字、顏色、圖像、邊框和陰影等。
Composite(渲染層合併):按照合理的順序合併圖層而後顯示到屏幕上。git

實際場景下,大概會有三種常見的渲染流程(也便是LayoutPaint步驟是可避免的):
github

 

 

結合渲染流程怎麼優化渲染性能呢?

結合上述的渲染流程,咱們能夠去針對性的分析並優化每一個步驟。web

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

優化JavaScript的執行效率,具體能夠作什麼?

動畫實現,避免使用setTimeout或setInterval,儘可能使用requestAnimationFrame

setTimeout(callback)setInterval(callback)沒法保證callback函數的執行時機,極可能在幀結束的時候執行,從而致使丟幀,以下圖:
瀏覽器

 

注意:jQuery的animate函數就是用setTimeout來實現動畫,能夠經過jquery-requestAnimationFrame這個補丁來用requestAnimationFrame替代setTimeout安全

 

把耗時長的JavaScript代碼放到Web Workers中去作

JavaScript代碼運行在瀏覽器的主線程上,與此同時,瀏覽器的主線程還負責樣式計算、佈局、繪製的工做,若是JavaScript代碼運行時間過長,就會阻塞其餘渲染工做,極可能會致使丟幀。
前面提到每幀的渲染應該在16ms內完成,但在動畫過程當中,因爲已經被佔用了很多時間,因此JavaScript代碼運行耗時應該控制在3-4毫秒。
若是真的有特別耗時且不操做DOM元素的純計算工做,能夠考慮放到Web Workers中執行。性能優化

var dataSortWorker = new Worker("sort-worker.js");

dataSortWorker.postMesssage(dataToSort);

// 主線程不受Web Workers線程干擾
dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = e.data;

    // Web Workers線程執行結束
    // ...
});

 

把DOM元素的更新劃分爲多個小任務,分別在多個frame中去完成

因爲Web Workers不能操做DOM元素的限制,因此只能作一些純計算的工做,對於不少須要操做DOM元素的邏輯,能夠考慮分步處理,把任務分爲若干個小任務,每一個任務都放到requestAnimationFrame中回調執行框架

var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);

requestAnimationFrame(processTaskList);

function processTaskList(taskStartTime) {
    var nextTask = taskList.pop();

    // 執行小任務
    processTask(nextTask);

    if (taskList.length > 0) {
        requestAnimationFrame(processTaskList);
    }
}

 

使用Chrome DevTools的Timeline來分析JavaScript的性能

打開Chrome DevTools > Timeline > JS Profile,錄製一次動做,而後分析獲得的細節信息,從而發現問題並修復問題。dom

 

下降樣式計算的範圍和複雜度,具體能夠作什麼?

添加或移除一個DOM元素、修改元素屬性和樣式類、應用動畫效果等操做,都會引發DOM結構的改變,從而致使瀏覽器須要從新計算每一個元素的樣式,對整個頁面或部分頁面從新佈局,這就是所謂的樣式計算。
樣式計算主要分爲兩步:建立一套匹配的樣式選擇器,爲匹配的樣式選擇器計算具體的樣式規則

下降樣式選擇器的複雜度

儘可能保持class的簡短,或者使用Web Components框架。

減小須要執行樣式計算的元素個數

因爲瀏覽器的優化,現代瀏覽器的樣式計算直接對目標元素執行,而不是對整個頁面執行,因此咱們應該儘量減小須要執行樣式計算的元素的個數

 

避免大規模、複雜的佈局,具體能夠作什麼?

佈局就是計算DOM元素的大小和位置的過程,若是你的頁面中包含不少元素,那麼計算這些元素的位置將耗費很長時間。
佈局的主要消耗在於:1. 須要佈局的DOM元素的數量;2. 佈局過程的複雜程度

儘量避免觸發佈局

當你修改了元素的屬性以後,瀏覽器將會檢查爲了使這個修改生效是否須要從新計算佈局以及更新渲染樹,對於DOM元素的「幾何屬性」修改,好比width/height/left/top等,都須要從新計算佈局。
對於不能避免的佈局,可使用Chrome DevTools工具的Timeline查看明細。

能夠查看佈局的耗時,以及受影響的DOM元素數量。

 

使用flexbox替代老的佈局模型

老的佈局模型以相對/絕對/浮動的方式將元素定位到屏幕上
Floxbox佈局模型用流式佈局的方式將元素定位到屏幕上
經過一個小實驗能夠看出兩種佈局模型的性能差距,一樣對1300個元素佈局,浮動佈局耗時14.3ms,Flexbox佈局耗時3.5ms

 


避免強制同步佈局事件的發生

前面提過,將一幀畫面渲染的屏幕上的流程是:

 

首先是JavaScript腳本,而後是Style,而後是Layout,可是咱們能夠強制瀏覽器在執行JavaScript腳本以前先執行佈局過程,這就是所謂的強制同步佈局。

 

requestAnimationFrame(logBoxHeight);

// 先寫後讀,觸發強制佈局
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');
}

在JavaScript腳本運行的時候,它能獲取到的元素樣式屬性值都是上一幀畫面的,都是舊的值。所以,若是你在當前幀獲取屬性以前又對元素節點有改動,那就會致使瀏覽器必須先應用屬性修改,結果執行佈局過程,最後再執行JavaScript邏輯。

避免連續的強制同步佈局發生

若是連續快速的屢次觸發強制同步佈局,那麼結果更糟糕。
好比下面的例子,獲取box的屬性,設置到paragraphs上,因爲每次設置paragraphs都會觸發樣式計算和佈局過程,而下一次獲取box的屬性必須等到上一步設置結束以後才能觸發。

function resizeWidth() {
    // 會讓瀏覽器陷入'讀寫讀寫'循環
    for (var i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = box.offsetWidth + 'px';
    }
}

// 改善後方案
var width = box.offsetWidth;
function resizeWidth() {
    for (var i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px';
    }
}

注意:可使用FastDOM來確保讀寫操做的安全,從而幫你自動完成讀寫操做的批處理,還能避免意外地觸發強制同步佈局或快速連續佈局

 

簡化繪製的複雜度、減小繪製區域,具體能夠作什麼?

繪製就是填充像素的過程,一般這個過程是整個渲染流程中耗時最長的一環,所以也是最須要避免發生的一環。
若是Layout被觸發,那麼接下來元素的Paint必定會被觸發。固然純粹改變元素的非幾何屬性,也可能會觸發Paint,好比背景、文字顏色、陰影效果等。

提高移動或漸變元素的繪製層

繪製並不是老是在內存中的單層畫面裏完成的,實際上,瀏覽器在必要時會將一幀畫面繪製成多層畫面,而後將這若干層畫面合併成一張圖片顯示到屏幕上。
這種繪製方式的好處是,使用transform來實現移動效果的元素將會被正常繪製,同時不會觸發其餘元素的繪製。

減小繪製區域

瀏覽器會把相鄰區域的渲染任務合併在一塊兒進行,因此須要對動畫效果進行精密設計,以保證各自的繪製區域不會有太多重疊。

簡化繪製的複雜度

能夠實現一樣效果的不一樣方式,咱們應該採用性能更好的那種。

經過Chrome DevTools來分析繪製複雜度和時間消耗,儘量下降這些指標

打開DevTools,按下鍵盤的ESC鍵,在彈出的面板中,選中rendering選項卡下的Enable paint flashing,這樣每當頁面發生繪製的時候,屏幕就會閃現綠色的方框。經過該工具能夠檢查Paint發生的區域和時機是否是能夠被優化。

經過Chrome DevTools中的Timeline > Paint選項能夠查看更細節的Paint信息

 

優先使用渲染層合併屬性、控制層數量,具體能夠作什麼?

使用transform/opacity實現動畫效果

使用transform/opacity實現動畫效果,會跳過渲染流程的佈局和繪製環節,只作渲染層的合併。

 

 

 

 

管理渲染層、避免過多數量的層

儘管提高渲染層看起來很誘人,但不能濫用,由於更多的渲染層意味着更多的額外的內存和管理資源,因此當且僅當須要的時候才爲元素建立渲染層。

* {
  will-change: transform;
  transform: translateZ(0);
}

使用Chrome DevTools來了解頁面的渲染層狀況

開啓Chrome DevTools > Timeline > Paint選項,而後錄製一段時間的操做,選擇單獨的幀,看到每一個幀的渲染細節,在ESC彈出框有個Layers選項,能夠看到渲染層的細節,有多少渲染層?爲什麼被建立?

 

對用戶輸入事件的處理函數去抖動(移動設備),具體能夠作什麼?

用戶輸入事件處理函數會在運行時阻塞幀的渲染,而且會致使額外的佈局發生。

避免使用運行時間過長的輸入事件處理函數

理想狀況下,當用戶和頁面交互,頁面的渲染層合併線程將接收到這個事件並移動元素。這個響應過程是不須要主線程參與,不會致使JavaScript、佈局和繪製過程發生。

可是若是被觸摸的元素綁定了輸入事件處理函數,好比touchstart/touchmove/touchend,那麼渲染層合併線程必須等待這些被綁定的處理函數執行完畢才能執行,也就是用戶的滾動頁面操做被阻塞了,表現出的行爲就是滾動出現延遲或者卡頓。

簡而言之就是你必須確保用戶輸入事件綁定的任何處理函數都可以快速的執行完畢,以便騰出時間來讓渲染層合併線程完成他的工做。

 


 

避免在輸入事件處理函數中修改樣式屬性

輸入事件處理函數,好比scroll/touch事件的處理,都會在requestAnimationFrame以前被調用執行。
所以,若是你在上述輸入事件的處理函數中作了修改樣式屬性的操做,那麼這些操做就會被瀏覽器暫存起來,而後在調用requestAnimationFrame的時候,若是你在一開始就作了讀取樣式屬性的操做,那麼將會觸發瀏覽器的強制同步佈局操做。

 

 

對滾動事件處理函數去抖動

經過requestAnimationFrame能夠對樣式修改操做去抖動,同時也可使你的事件處理函數變得更輕

function onScroll(evt) {
    // Store the scroll value for laterz.
    lastScrollY = window.scrollY;

    // Prevent multiple rAF callbacks.
    if (scheduledAnimationFrame) {
        return;
    }

    scheduledAnimationFrame = true;
    requestAnimationFrame(readAndUpdatePage);
}

window.addEventListener('scroll', onScroll);

 

總結點什麼?

網站性能優化是一個有必定門檻的細緻活,須要對瀏覽器的機制有很好的理解,同時也應該學會利用Chrome DevTools去分析並解決實際問題,關於Chrome DevTools的學習我會專門開一篇博客來說解,同時會結合具體的性能問題來分析。

原文地址https://www.jianshu.com/p/a32b890c29b1
相關文章
相關標籤/搜索