優化關鍵渲染路徑

瀏覽器渲染原理

image
image

瀏覽器渲染流程css

JavaScript

JavaScript實現動畫效果,DOM元素操做等。html

CSSOM

肯定每一個DOM元素應該應用什麼CSS規則。web

注意: CSS選擇器越詳細,匹配工做越多,匹配節點越慢。瀏覽器

RenderTree(渲染樹)

RenderTree包含了渲染網頁所需的節點, 無需渲染的節點不會被添加到RenderTree中。 如:<head>,display: none的節點網絡

注意:由於設置了visibility:hidden的元素雖不可見,但仍然佔有空間,仍會被添加到RenderTree。app

Layout(佈局)

計算每一個DOM元素在最終屏幕上顯示的大小和位置。因爲web頁面的元素佈局是相對的,因此其中任意一個元素的位置發生變化,都會聯動的引發其餘元素髮生變化,這個過程叫reflow。異步

注意:影響web性能的一個重要的問題就是repaint和reflow。async

觸發Layout

  • 屏幕旋轉函數

  • 瀏覽器視窗改變佈局

  • 與大小位置相關的CSS屬性改變

Paint(繪製)

根據background,border,box-shadow等樣式,將Layout生成的區域填充爲最終將顯示在屏幕上的像素

Composite(渲染層合併)

按照合理的順序合併圖層而後顯示到屏幕上。

三種渲染流程

實際場景下,大概會有三種常見的渲染流程:

  • JavaScript -> CSS -> Layout -> Paint -> Composite

  • JavaScript -> CSS -> Paint -> Composite

  • JavaScript -> CSS -> Composite

注意:Layout和Paint步驟是可避免的

優化CSS

瀏覽器會在DOM和CSSOM加載完開始渲染頁面。

避免CSS阻塞初次渲染

<style>/* styles here */</style>
<link rel="stylesheet" href="index.css">

經過以上兩種方式定義的CSS,均會阻塞初次渲染。瀏覽器會在解析完CSS後,再進行渲染。這是爲了防止樣式突變帶來的抖動。經過link標籤引入的CSS阻塞的時間可能更長,由於加載它須要一個網絡來回時間

media query

<link rel="stylesheet" href="index_print.css" media="print">

此樣式表仍會加載。當瀏覽器環境不匹配媒體查詢條件時,該樣式表不會阻塞渲染。咱們可針對不一樣媒體環境拆分CSS文件,併爲link標籤添加媒體查詢,避免爲了加載非關鍵CSS資源,而阻塞初次渲染

經過DOM API添加link

var style = document.createElement('link');
style.rel = 'stylesheet';
style.href = 'index.css';
document.head.appendChild(style);

該方法不會阻塞初次渲染。

preload

<link rel="preload" href="index_print.css" as="style" onload="this.rel='stylesheet'">

rel不是stylesheet,所以不會阻塞渲染。preload是resoure hint規範中定義的一個功能,resource hint經過告知瀏覽器提早創建鏈接或加載資源,以提升資源加載的速度。瀏覽器遇到遇到標記爲preload的link時,會開始加載,當onload事件發生時,將rel改成stylesheet,便可應用此樣式。

總結

引入CSS資源的方法 是否阻塞初次渲染
<link rel="stylesheet" href="index.css" />
經過document.write寫入以上標籤
經過DOM API插入HTMLLinkElement對象
使用preload方式載入CSS
爲link添加media query 當媒體查詢不匹配時,不會阻塞

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

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

JavaScript優化

避免Javascript阻塞HTML Parser(解析器)

<-- inline js -->
<script>/* app logics here */</script>

<-- external js -->
<script src="somescript.js"></script>

經過以上兩種方式引入js均會阻塞HTML parser,於是會阻塞出如今腳本後面的HTML標記的渲染。而外部script阻塞的時間通常更長,由於可能包含了一個網絡來回時間。

Javascript能夠經過document.write修改HTML文檔流,所以在執行js時,瀏覽器會暫停解析DOM的工做。

CSS阻塞JS

<-- inline js -->
<script>/* app logics here */</script>

<-- external js -->
<script src="somescript.js"></script>

經過以上兩種方式引入的JS均會被CSS阻塞,因爲這些Javascript可能會讀取或修改CSSOM,所以需等待CSSOM構造完成後,它們才能執行

將資源放到文檔底部,延遲js執行

<html>
  <head></head>
  <body>
    <h1>世界上最美麗的語言是什麼?</h1>
    <button>See answer</button>
    <!-- index.js內容:
      爲button標籤添加點擊事件,點擊後,alert答案
     -->
    <script src="index.js"></script>
    <!-- 百度統計代碼 -->
    <script src="tongji.js"></script>
  </body>
</html>

使用defer延遲腳本執行

<html>
  <head>
    <!-- index.js內容:
      爲button標籤添加點擊事件,點擊後,alert答案
     -->
    <script src="index.js" defer></script>
    <!-- 百度統計代碼 -->
    <script src="tongji.js" defer></script>
  </head>
  <body>
    <h1>世界上最美麗的語言是什麼?</h1>
    <button>See answer</button>
  </body>
</html>

當script標籤擁有defer屬性時,該腳本會被推遲到整個HTML文檔解析完後,再開始執行。被defer的腳本,在執行時會嚴格按照在HTML文檔中出現的順序執行

注意: 使用defer時,瀏覽器會保證腳本按照在文檔中出現的順序執行

使用async異步加載腳本

<html>
  <head>
    <!-- index.js內容:
      document.addEventListener('DOMContentLoaded', function() {
        document.querySelector('p').onclick=function() {
          alert('surprise')
        }
      });    
     -->
    <script src="index.js" async></script>
    <!-- 百度統計代碼 -->
    <script src="tongji.js" async></script>
  </head>
  <body>
    <p>Hello World</p>
  </body>
</html>
  • 當script標籤擁有async屬性時,該腳本不會再阻塞HTML parser。且不會被CSS阻塞。

  • 腳本只要加載完成,即可開始執行。

  • 被async的腳本,在執行時會不會嚴格按照在HTML文檔中出現的順序執行

  • async適用於無依賴的獨立資源

image

總結

引入JS資源的方法 是否阻塞文檔內容初次渲染
在head中引入外部腳本<script src="index.js"></script>或內聯腳本<script>/* app logics */</script>
將腳本放到body底部
爲腳本添加defer屬性
爲腳本添加async屬性

用requestAnimationFrame代替setTimeout或setInterval

setTimeout(callback)和setInterval(callback)沒法保證callback函數的執行時機,極可能在幀結束的時候執行,從而致使丟幀。requestAnimationFrame(callback)能夠保證callback函數在每幀動畫開始的時候執行。

image

幀丟失

用Web Worker去處理耗時的JS代碼

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線程執行結束
    // ...
});

用多個frame去處理DOM元素的更新

因爲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);
    }
}

Layout優化

避免觸發佈局

當修改了元素的屬性以後,瀏覽器將會檢查爲了使這個修改生效是否須要從新計算佈局以及更新渲染樹,對於DOM元素的「幾何屬性」修改,好比width/height/left/top等,都須要從新計算佈局。

使用flexbox替代老的佈局模型

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

其餘優化

Font

Font阻塞內容渲染

  • 瀏覽器爲了不FOUT(Flash Of Unstyled Text),會盡可能等待字體加載完成後,再顯示應用了該字體的內容

  • 只有當字體超過一段時間仍未加載成功時,瀏覽器纔會降級使用系統字體。每一個瀏覽器都規定了本身的超時時間

  • 但這也帶來了FOIT(Flash Of Invisible Text)問題。內容沒法儘快地被展現,致使空白。

相關文章
相關標籤/搜索