摘要:縮短的長度能夠有效降低首屏時間。即便你使用打包工具只引用了一個外部腳本文件,但是如果這個腳本文件的傳輸延遲和執行延遲,會導致后面的非關鍵資源的請求被延遲,雖然這不會減慢的首屏時間。
以通俗的方式理解關鍵渲染路徑
1. 什么是 CRP ?我在看了 google 的 Critical Rendering Path (中文)后, 想把 CRP(Critical Rendering Path) 用通俗易懂的方式描述出來。 官方文檔當然是描述最為詳盡且可靠的。 文章里的有些圖片是直接引用自官方文檔。 如果存在侵權, 立刻刪除。
游覽器從開始請求 HTML 文檔, 到首次渲染到屏幕上(首屏), 背后需要做很多的事情, 這一連串事情就是 CRP 。 開發 app 的時候很多優化都是和縮短 CRP 有關的。 縮短 CRP 的長度能夠有效降低首屏時間。 這也是理解 CRP 最大的好處。
CRP 大概包括兩個部分: 1、 本地加載渲染, 2、 網絡請求 。
2. 游覽器的渲染原理從游覽器加載 HTML 文檔到首屏, 中間發生了什么?
TL;DR
游覽器解析 HTML 形成 DOM 樹。
游覽器解析 CSS 代碼輸出 CSSOM 。
DOM 和 CSSOM 一起構造出一顆 Render Tree 。
游覽器對 Render Tree 上的節點計算精確位置(布局)。
使用 Render Tree 繪制 (Painting) 到屏幕上。
DOM 用來描述文檔結構, 因而是樹形結構。 getElementById , querySelector 等 API 就是針對DOM操作的。 CSSOM 用來描繪 DOM 上的節點的樣式, 所以圖中的 CSSOM 的根節點是 body , 這是因為 head 標簽沒有樣式信息。 樣式的繼承過程就是發生在構造 CSSOM 的階段。 當你使用 element.style 訪問元素的樣式的時候, 其實訪問的是CSSOM 上對應的節點。 然后利用 DOM 和 CSSOM 生成 Render Tree , 這中間做了很多事情, 比如說如果檢測到一個元素所對應的 CSSOM 節點存在 display: none , 那這個節點將不會輸出到 Render Tree 。
接下來要利用 Render Tree 計算每個節點在視口上具體的位置, 這就是布局。 布局階段輸出 CSS 的 “盒子模型”, 最后利用這些盒子, 繪制到屏幕上 (Painting) , 在這個過程中如果檢測到 visibility: hidden , 游覽器會將其繪制成一個空白(這里的空白是完完全全的空白, 而不是白色。。。)
現在對于 visibility: hidden 和 display: none 的理解應該加深了不少, 具體的區別可以 google 。
這就是游覽器的大概的渲染過程。
3. 深入 CRP其實討論 CRP 更多的時候是在討 CSS 和 JS 。 因為 CSS 和 JS 會阻塞渲染。 為什么說 CSS 和 JS 會阻塞渲染? 很難想象如果游覽器先呈現一段沒有樣式的頁面,然后突然刷新, 這是一種及其糟糕的體驗。 而 JS 腳本的執行會訪問 DOM 和 CSSOM, 為什么 JS 是同步加載, 而不是異步加載呢? 游覽器為什么不像處理樣式文件一樣處理腳本文件呢? 這其實很好理解, 腳本文件一般包含著你應用的邏輯, 如果腳本都是異步加載, 那應用的邏輯豈不是亂套。。
什么是 CRP 長度?
獲取所有阻塞資源(關鍵資源)所需的往返次數, 比如說樣式文件, 腳本文件; 圖片不屬于關鍵資源, 因為圖片不會導致阻塞游覽器渲染。
關于 CRP 中幾個關鍵的時間點:
domLoading 這是整個 CRP 的開始的時間點。
domInteractive 游覽器剛好構建完 DOM 的時間點。
domContentLoaded DOM 構建完成, 且沒有任何樣式會阻塞腳本允許的時間點。 意思就是當沒有腳本文件執行的時候, DOM 構建完成時就到達這個時間點, 畢竟沒有腳本需要執行怎么會存在阻塞呢? 不過這種情況很少見。 當有腳本文件要執行的時候, 樣式( CSSOM )會阻塞腳本文件執行, 此時要等到樣式全部就緒的時候才會到達這個時間點。 所以, 當存在腳本文件的時候, 一般 domContentLoaded 會往后推移許多。
domCompelete 表示所有資源都已經下載完成, 包括圖片, 字體等。 這個時間點將觸發 onload 事件。
如圖:
talk is cheap show me the code
HTML 文件:
Test
這段代碼首先在 head 里引用了樣式文件, 在文檔的最后引用了 script 文件, 這是因為 script 標簽會導致游覽器阻塞 DOM 的解析, 游覽器甚至會花時間等待 script 引用的腳本資源的請求過程(同步加載), 直至請求響應后且解析完腳本后才會將控制權交還給游覽器來繼續解析 DOM 。 因此腳本執行時, 很可能 DOM 沒有創建完成, 將 script 放到最后是明智之舉。 這里一共請求了三個資源, 但是幾乎都是同時發起的, 因此算作一次往返, 所以 CRP 長度是1。
使用 DevTools 的 Timeline 查看頁面加載情況。 如果你對 DevTools 不熟悉, 可以看下 Chrome DevTools 快速入門 的官網文檔 。 實際上這里并用不到很多這方面的知識。 稍微有所了解就行。 在使用 Timeline 的時候要注意開啟隱身模式, 不然會存在諸如游覽器插件等干擾因素。
上面的代碼的加載性能截圖:
可以看到圖中有兩條非常接近的垂直的線, 藍色表示觸發 DOMContentLoaded 事件的時間點, 紅色表示觸發 onload 事件的時間點。兩個時間點非常接近, 這是由于游覽器在請求外部腳本時會等待其響應, 因此將 DOMContentLoaded 事件向后推遲了。 這里的 CRP 長度是1。 假如沒有腳本文件的請求, 那我們應用的 DOMContentLoaded 將在 DOM 解析完成后發生, 而不至于等到所有樣式就緒( CSS 解析完成)。 這里即便是將腳本文件內聯到 HTML 文檔中也是一樣的結果。 因為腳本代碼的執行會訪問 CSSOM , 游覽器在解析腳本代碼的時候會等待所有 CSSOM 就緒才開始執行腳本代碼。 因此效果是一樣的。
這是上面代碼的 CRP 流程圖:
可以看到在 T1 到 T2 這個時間段, 游覽器解析 DOM 的操作是被阻塞的。 即便內聯了腳本文件。
因為大多數的 JavaScript 框架的邏輯開始部分都是從 DOMContentLoaded 事件點開始的(因為 DOMContentLoaded 總比 onload 快, onload 會等到圖片等資源都就緒才觸發, 會拖慢首屏時間)。 因此, 提前 DOMContentLoaded 是很有必要的。
一般的 CRP 流程是這樣的:
圖上有兩個灰色的方塊, 這兩個方塊是導致 CRP 時間變長的罪魁禍首!
4. 減少 CRP , 出發!縮短 CRP 就是盡快使 DOMContentLoaded 事件產生, 以便盡快執行 APP 邏輯, 不要拖到 onload , 節約一分一秒! 因為 DOMContentLoaded 產生后, 就會構建 Render Tree , 然后順水推舟, 用戶就能看到 APP 了。
不要引用過多的腳本文件!
在前幾年的前端開發中, 喜歡將對腳本的引用寫在 HTML 文件里, 這會導致 DOMContentLoaded 觸發大大延遲。 因為每一次解析一個腳本都會阻塞游覽器構建 DOM 。 當一個 HTML 文件引用幾十個外部腳本文件時, 那肯定是一場災難! 所幸最近幾年各種前端打包工具使得這個問題得以解決。
在筆試騰訊的時候遇到一個問題: 使用 HTTP2 的時候還有沒有將 JS 文件打包的必要? 答案是: 有必要, 而且是必須的! 雖然 HTTP2 可以實現同一個 TCP 連接里的所有資源的并行傳輸(使用流)。 但是游覽器處理外部腳本文件的策略不會變! 所以依然有打包的必要!
將外部腳本的引用放到 HTML 文件的最后, 或者使用 defer 屬性。
即便你使用打包工具只引用了一個外部腳本文件, 但是如果這個腳本文件的傳輸延遲和執行延遲, 會導致后面的非關鍵資源的請求被延遲, 雖然這不會減慢 APP 的首屏時間。 但是圖片等非關鍵資源的呈現時間卻被延遲了。 使用defer屬性可以將執行時間推遲到 domContentLoaded 時間點后。 DOMContentLoaded也是這個時間點后觸發。那會先執行 defer 還是先觸發 DOMContentLoaded 事件? 在 Chrome 游覽器下的順序是先執行 defer 再觸發DOMContentLoaded 事件。
對外部腳本使用 async 屬性
async 屬性告訴游覽器異步請求外部腳本文件, 不要阻塞在這里。 這樣可以使得游覽器繼續構建 DOM 。 或者處理后面的資源請求。
將樣式文件請求置于 head 標簽內。
盡早在 HTML 文檔內指定所有 CSS 資源,以便瀏覽器盡早發現 link 標記并盡早發出 CSS 請求。
盡量不要使用 @import 指令。
CSS 中的 @import 表示在一個樣式文件中導入另外一個樣式文件。 一個樣式文件 import 另外一個樣式文件時, 只有在這個樣式文件被收到且被解析完成才會 import 。 這樣會增加 CRP 長度。
內聯樣式
使用 style 標簽將樣式內聯到 HTML 文件內, 雖然這樣做會增大 HTML 文件的體積。 但是卻可以減少 CRP 長度。 同時也避免了腳本代碼的執行被阻塞的情況。
為什么沒有內聯腳本呢? 因為絕大多數情況下不可能做到完全的內聯樣式。 這個時候即便使用內聯腳本也會因為 CSSOM 而被阻塞, 會阻止 DOM 構建。 因而這并不是一種優化措施。
減少你的關鍵資源的大小和數量
這是優化最好的手段。
Else
這里并沒有提到如何去優化應用的邏輯來增加速度, 因為這并不屬于 CRP 。
5. 常用優化工具安利。Lighthouse
Lighthouse 是一個網絡應用審核工具, 可以幫助你找到 CRP 的瓶頸所在。
Lighthouse 的截圖:
Navigation Time API
這個內置的 API 可以幫助你記錄下各個時間點的具體時間值, 上面說的那幾個時間點都有。
6. 最后的最后如果你覺得有哪里寫的不是很恰當或你不能理解的, 可以在評論里告訴我。。
如果你覺得我寫的內容對你有所幫助。 可以選擇關注我。
我的博客地址是: mrcode
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82370.html
摘要:縮短的長度能夠有效降低首屏時間。即便你使用打包工具只引用了一個外部腳本文件,但是如果這個腳本文件的傳輸延遲和執行延遲,會導致后面的非關鍵資源的請求被延遲,雖然這不會減慢的首屏時間。 以通俗的方式理解關鍵渲染路徑 我在看了 google 的 Critical Rendering Path (中文)后, 想把 CRP(Critical Rendering Path) 用通俗易懂的方式描述出...
摘要:可見對一個頁面正確渲染很重要。和標簽對用于標識頁面的頭部區域,和之間的內容都屬于頭部區域中的內容。是一個輔助性標簽,對頁面可以進行很多方面的特性的設置。當頁面沒有設置字符集時,瀏覽器會使用默認的字符編碼顯示。 showImg(https://segmentfault.com/img/bVbjNkI?w=900&h=383); 往期回顧 在 1.2 節介紹 HTML 語言時講到:HTML...
摘要:客戶端發送包到服務器,并進入狀態,等待服務器確認。再進一步接收到客戶端的就進入狀態。通常情況下連接就是連接,因此連接一旦建立通訊雙方開始互發數據進行通信,直到其中一方或雙方斷開連接為止。統一資源定位符。 本文旨在用最通俗的語言講述最枯燥的基本知識 面試過前端的老鐵都知道,對于前端,面試官喜歡一開始先問些HTML5新增元素啊特性啊,或者是js閉包啊原型啊,或者是css垂直水平居中怎么實現...
摘要:客戶端發送包到服務器,并進入狀態,等待服務器確認。再進一步接收到客戶端的就進入狀態。通常情況下連接就是連接,因此連接一旦建立通訊雙方開始互發數據進行通信,直到其中一方或雙方斷開連接為止。統一資源定位符。 本文旨在用最通俗的語言講述最枯燥的基本知識 面試過前端的老鐵都知道,對于前端,面試官喜歡一開始先問些HTML5新增元素啊特性啊,或者是js閉包啊原型啊,或者是css垂直水平居中怎么實現...
摘要:客戶端發送包到服務器,并進入狀態,等待服務器確認。再進一步接收到客戶端的就進入狀態。通常情況下連接就是連接,因此連接一旦建立通訊雙方開始互發數據進行通信,直到其中一方或雙方斷開連接為止。統一資源定位符。 本文旨在用最通俗的語言講述最枯燥的基本知識 面試過前端的老鐵都知道,對于前端,面試官喜歡一開始先問些HTML5新增元素啊特性啊,或者是js閉包啊原型啊,或者是css垂直水平居中怎么實現...
閱讀 1660·2021-11-16 11:41
閱讀 2458·2021-11-08 13:14
閱讀 3106·2019-08-29 17:16
閱讀 3079·2019-08-29 16:30
閱讀 1843·2019-08-29 13:51
閱讀 356·2019-08-23 18:38
閱讀 3223·2019-08-23 17:14
閱讀 630·2019-08-23 15:09