優化關鍵渲染路徑

上個月,我寫了一篇文章介紹什麼是「關鍵渲染路徑」,其實目的是爲了給這篇文章作一個鋪墊,本文將談談如何優化關鍵渲染路徑(本文將假設您已經閱讀過《關鍵渲染路徑》這篇文章或已經懂得了什麼是「關鍵渲染路徑」)。php

優化關鍵渲染路徑能夠提高網頁的渲染速度,從而獲得一個更好的用戶體驗。css

如何優化關鍵渲染路徑?

優化關鍵渲染路徑有不少種方法與狀況,不一樣狀況下優化方式也各不相同,初步看起來這些優化方法五花八門,知識很是的零散。html

但在這些看似零散的知識中,咱們會發現一些規律,將這些規律總結起來後,能夠得出一個結論:到目前爲止,只有三種因素能夠影響關鍵渲染路徑的耗時。而全部的優化方式,都是在儘量的針對這三種因素進行優化。git

這三種因素分別是:github

  • 關鍵資源的數量
  • 關鍵路徑的長度
  • 關鍵字節的數量

切記,很是重要,全部優化關鍵渲染路徑的方法,都是在優化以上三種因素。由於只有這三種因素能夠影響關鍵渲染路徑。瀏覽器

關鍵資源指的是那些能夠阻塞頁面首次渲染的資源。例如JavaScript、CSS都是能夠阻塞關鍵渲染路徑的資源,這些資源就屬於「關鍵資源」。關鍵資源的數量越少,瀏覽器處理渲染的工做量就越少,同時CPU及其餘資源的佔用也越少。緩存

關鍵路徑中的每一步耗時越長,因爲阻塞會致使渲染路徑的總體耗時變長。關鍵路徑的長度指的是關鍵渲染路徑的總耗時。關鍵渲染路徑的長度會受到不少因素的影響。例如:關鍵資源的網絡狀況、關鍵資源的數量、關鍵資源的字節大小、關鍵資源的依賴關係等。網絡

關鍵字節的數量指的是關鍵資源的字節大小,瀏覽器要下載的資源字節越小,則下載速度與處理資源的速度都會更快。一般不少優化方法都是針對關鍵字節的數量進行優化。例如:壓縮。app

優化DOM

在關鍵渲染路徑中,構建渲染樹(Render Tree)的第一步是構建DOM,因此咱們先討論如何讓構建DOM的速度變得更快。dom

HTML文件的尺寸應該儘量的小,目的是爲了讓客戶端儘量早的接收到完整的HTML。一般HTML中有不少冗餘的字符,例如:JS註釋、CSS註釋、HTML註釋、空格、換行。更糟糕的狀況是我見過不少生產環境中的HTML裏面包含了不少廢棄代碼,這多是由於隨着時間的推移,項目愈來愈大,因爲種種緣由從歷史遺留下來的問題,不過無論怎麼說,這都是很糟糕的。對於生產環境的HTML來講,應該刪除一切無用的代碼,儘量保證HTML文件精簡。

總結起來有三種方式能夠優化HTML:縮小文件的尺寸(Minify)使用gzip壓縮(Compress)使用緩存(HTTP Cache)

縮小文件的尺寸(Minify)會刪除註釋、空格與換行等無用的文本。

本質上,優化DOM實際上是在儘量的減少關鍵路徑的長度與關鍵字節的數量

優化CSSOM

與優化DOM相似,CSS文件也須要讓文件儘量的小,或者說全部文本資源都須要。CSS文件應該刪除未使用的樣式、縮小文件的尺寸(Minify)、使用gzip壓縮(Compress)、使用緩存(HTTP Cache)。

除了上面提到的優化策略,CSS還有一個能夠影響性能的因素是:CSS會阻塞關鍵渲染路徑

CSS是關鍵資源,它會阻塞關鍵渲染路徑也並不奇怪,但一般並非全部的CSS資源都那麼的『關鍵』。

舉個例子:一些響應式CSS只在屏幕寬度符合條件時纔會生效,還有一些CSS只在打印頁面時才生效。這些CSS在不符合條件時,是不會生效的,因此咱們爲何要讓瀏覽器等待咱們並不須要的CSS資源呢?

針對這種狀況,咱們應該讓這些非關鍵的CSS資源不阻塞渲染

實現這一目的很是簡單,咱們只須要將不阻塞渲染的CSS移動到單獨的文件裏。例如咱們將打印相關的CSS移動到print.css,而後咱們在HTML中引入CSS時,添加媒體查詢屬性print,代碼以下:

<link href="print.css" rel="stylesheet" media="print">
複製代碼

上面代碼添加了media="print"屬性,因此上面CSS資源僅用於打印。添加了媒體查詢屬性後,瀏覽器依然會下載該資源,但若是條件不符合,那麼它就再也不阻塞渲染,也就是變成了非阻塞的CSS

咱們能夠寫個DEMO測試一下:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="https://static.xx.fbcdn.net/rsrc.php/v3/y6/l/1,cross/9Ia-Y9BtgQu.css">
</head>
<body>
    Hello
</body>
</html>
複製代碼

上面代碼使用Chrome開發者工具的性能面板捕獲後的性能圖以下:

阻塞的CSS資源性能捕獲圖

從上圖中的首次繪製(First Paint)時間是在1200ms的位置,能夠看到這個時間是瀏覽器加載CSS完畢後,並且能夠看到Network欄中CSS顯示Highest表明高優先級。

添加了媒體查詢語句後,捕獲出來的性能圖以下:

非阻塞的CSS資源性能捕獲圖

首次繪製時間在不到100ms的位置,和domcontentloaded事件差很少的時間觸發。同時CSS資源變成了Lowest,表示低優先級。

能夠看到,瀏覽器依然會下載該CSS資源,但它再也不阻塞渲染。

上面提供的方法是針對那些不須要生效的CSS資源,若是CSS資源須要在當前頁面生效,只是不須要在首屏渲染時生效,那麼爲了更快的首屏渲染速度,咱們能夠將這些CSS也設置成非關鍵資源。只是咱們須要一些比較hack的方式來實現這個需求:

<link href="style.css" rel="stylesheet" media="print" onload="this.media='all'">
複製代碼

上面代碼先把媒體查詢屬性設置成print,將這個資源設置成非阻塞的資源。而後等這個資源加載完畢後再將媒體查詢屬性設置成all讓它當即對當前頁面生效。

經過這樣的方式,咱們既可讓這個資源不阻塞關鍵渲染路徑,還可讓它加載完畢後對當前頁面生效。

相似的方案有不少,代碼以下:

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

<link rel="alternate stylesheet" href="style.css" onload="this.rel='stylesheet'">
複製代碼

上面兩種方式都能實現一樣的效果。

關於CSS的加載有這麼多門道,到底怎樣纔是最佳實踐?答案是:Critical CSS

Critical CSS的意思是:把首屏渲染須要使用的CSS經過style標籤內嵌到head標籤中,其他CSS資源使用異步的方式非阻塞加載。

CSS資源在構建渲染樹時,會阻塞JavaScript,因此咱們應該保證全部與首屏渲染無關的CSS資源都應該被標記爲非關鍵資源。

因此Critical CSS從兩個方面解決了性能問題:

  1. 減小關鍵資源的數量(將全部與首屏渲染無關的CSS使用異步非阻塞加載)
  2. 減小關鍵路徑的長度(將首屏渲染須要的CSS直接內嵌到head標籤中,移除了網絡請求的時間)。

避免使用@import

你們應該都知道要避免使用@import加載CSS,實際工做中咱們也不會這樣去加載CSS,但這究竟是爲何呢?

這是由於使用@import加載CSS會增長額外的關鍵路徑長度。舉個例子:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="http://127.0.0.1:8887/style.css">
    <link rel="stylesheet" href="https://lib.baomitu.com/CSS-Mint/2.0.6/css-mint.min.css">
</head>
<body>
    <div class="cm-alert">Default alert</div>
</body>
</html>
複製代碼

上面這段代碼使用link標籤加載了兩個CSS資源。這兩個CSS資源是並行下載的。咱們使用Chrome開發者工具的Performance面板捕獲出的結果以下圖所示:

使用link標籤加載樣式

從圖中用紅色方框圈出來的位置能夠看出兩個CSS是並行加載的,首次繪製時間取決於CSS加載時間較長的資源加載時間。

如今咱們改成使用@import加載資源,代碼以下:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="http://127.0.0.1:8887/style.css">
</head>
<body>
    <div class="cm-alert">Default alert</div>
</body>
</html>
複製代碼
/* style.css */
@import url('https://lib.baomitu.com/CSS-Mint/2.0.6/css-mint.min.css');
body{background:red;}
複製代碼

代碼中使用link標籤加載一個CSS,而後在CSS文件中使用@import加載另外一個CSS。使用Chrome開發者工具再次捕獲出的結果以下圖所示:

使用@import加載CSS

能夠看到兩個CSS變成了串行加載,前一個CSS加載完後再去下載使用@import導入的CSS資源。這無疑會致使加載資源的總時間變長。從上圖能夠看出,首次繪製時間等於兩個CSS資源加載時間的總和。

因此避免使用@import是爲了下降關鍵路徑的長度。

異步JavaScript

全部文本資源都應該讓文件儘量的小,JavaScript也不例外,它也須要刪除未使用的代碼、縮小文件的尺寸(Minify)、使用gzip壓縮(Compress)、使用緩存(HTTP Cache)。

與CSS資源類似,JavaScript資源也是關鍵資源,JavaScript資源會阻塞DOM的構建。而且JavaScript會被CSS文件所阻塞。爲了不阻塞,能夠爲script標籤添加async屬性。

咱們舉個例子:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="https://static.xx.fbcdn.net/rsrc.php/v3/y6/l/1,cross/9Ia-Y9BtgQu.css">
</head>
<body>
    <p class='_159h'>aa</p>
    <script src="http://qiniu.bkt.demos.so/static/js/app.53df42d5b7a0dbf52386.js"></script>
</body>
</html>
複製代碼

上面這段代碼,分別加載了CSS資源和JavaScript資源,咱們使用Chrome開發者工具的Performance面板捕獲出的結果以下圖所示:

同步加載JS資源

從捕獲出的結果能夠看到,JS資源加載完畢後,須要等待CSS資源加載完並構建出CSSOM以後纔會執行JS,而且JS會將DOM阻塞,因此最終domcontentloaded事件在350ms與400ms之間觸發。

咱們將script標籤添加async屬性:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="https://static.xx.fbcdn.net/rsrc.php/v3/y6/l/1,cross/9Ia-Y9BtgQu.css">
</head>
<body>
    <p class='_159h'>aa</p>
    <script async src="http://qiniu.bkt.demos.so/static/js/app.53df42d5b7a0dbf52386.js"></script>
</body>
</html>
複製代碼

使用Chrome開發者工具捕獲出的結果以下圖所示:

異步加載JS

從圖中能夠看到,JS加載完後再也不須要等待CSS資源,而且也再也不阻塞DOM的構建,最終domcontentloaded事件在50ms與100ms之間觸發。與以前相比,domcontentloaded事件觸發時間提早了300ms。

能夠看到,在關鍵渲染路徑中優化JavaScript,目的是爲了減小關鍵資源的數量

總結

該篇文章詳細介紹瞭如何優化關鍵渲染路徑。

關鍵渲染路徑是瀏覽器將HTML,CSS,JavaScript轉換爲屏幕上所呈現的實際像素的具體步驟。而優化關鍵渲染路徑能夠提升網頁的呈現速度,也就是首屏渲染優化。

你會發現,咱們介紹的內容都是如何優化DOM,CSSOM以及JavaScript,由於一般在關鍵渲染路徑中,這些步驟的性能最差。這些步驟是致使首屏渲染速度慢的主要緣由。

相關文章
相關標籤/搜索