現在讓我們看看瀏覽器從網絡上加載資源所耗費的時間(我們忽略從緩存以及從CDN等中間商網絡上加載資源),我們首先要知道的是:
- 一個到無服務的網路往返?(傳播延遲) 大約100ms
- 服務器對于HTML文檔的響應大約100ms,對于其他資源大約10ms
The Hello World experience
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>Critical Path: No Style</title></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div></body> </html>
我們只是加載了時間和網頁——沒有任何的js和css資源等——我們的頁面非常簡單,下面我們看看谷歌瀏覽器開發者工具里記錄下載用的時間:
HTML文檔大約花費200ms來加載。注意上面的藍色透明進度條表明了網路上的一個往返,這里我們的HTML文檔非常的小(小于4k),我們只是進行了簡單的文件抓取工作,一半的時間來等待網絡響應,一般的時間花費在服務器響應上,總用時大于200ms。
一旦HTML文檔內容可以使用,瀏覽器開始解析HTML內容(全部是字節),將其轉化成tokens,然后建立DOM樹。注意在工具條最下面顯示了DOMContentLoaded事件花費的時間是216毫秒,也就是對應的藍色垂直實線。在藍色透明進度條和藍色垂直實線之間的時間就是瀏覽器建立DOM樹的時間——在上例中只是花費了幾毫秒。
注意我們的“awesome photo”沒有阻塞任何的DOMContentLoaded事件,所以我們可以直接構造render tree甚至將內容顯示在屏幕上,不用等待圖片資源:不是所有的資源都會阻塞渲染!所以,在談論渲染過程的時候,我們只是談論了js,css和html標記。圖片雖然不會阻塞渲染,但是我們仍舊應該讓其迅速加載顯示。
但是,“load”事件(也被叫做"onload")事件卻被圖片加載阻塞:因為load事件是在所有資源加載完畢之后觸發,上圖中也即是最后的紅色垂直實線,也就是工具條最下面顯示的load:335ms,這也是瀏覽器加載器停止加載的時間點。
Adding JavaScript and CSS into the mix
實際的編程中肯定需要css和js,所以讓我們在其中加入css和js:
<html><head><title>Critical Path: Measure Script</title><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet"></head><body onload="measureCRP()"><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script src="timing.js"></script></body> </html>
在加入js和css之前:
在加入js和css之后
css文件和js文件都在異步同時被加載,這是好事!注意load和DOMContentLoaded和之前的微妙的不同:
- 不像只是簡單的HTML文件(不包含任何的css和js),包含js和css的文件還需要將css編程CSSOM樹,以及CSSOM樹和HTML樹相互結合形成render tree的過程。
- 因為js文件可能要處理CSSOM,所以css文件必須在執行js文件之前執行!并且css的下載將會阻塞domContentLoaded事件。
如果我們將js直接寫在html內部呢?
外部js導入:
直接書寫js:
雖然我們減少了一次加載請求,但是事件是差不多的,因為在解析script標簽的時候將會阻塞直到CSSOM被創建完成。同時,從上圖中我們可以看見,css和js文件的加載是平行進行的。
所以,我們有什么策略來減少事件呢?
讓我們在js文件中加入async異步關鍵字:
<html><head><title>Critical Path: Measure Async</title><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet"></head><body onload="measureCRP()"><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script async src="timing.js"></script></body> </html>
?
?
同步內部加載js:
異步外部加載js:
可以看見domContentLoade事件在HTML解析完成后馬上執行:瀏覽器知道了不要在處理js中對其進行阻塞,因為沒有了其他渲染的阻塞,CSSOM構造可以同時進行。
我們可以同時在文件內部書寫js和css文件內容,而不是外部引入:
<html><head><title>Critical Path: Measure Inlined</title><meta name="viewport" content="width=device-width,initial-scale=1"><style>p { font-weight: bold }span { color: red }p span { display: none }img { float: right }</style></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script>var span = document.getElementsByTagName('span')[0];span.textContent = 'interactive'; // change DOM text content span.style.display = 'inline'; // change CSSOM property// create a new element, style it, and append it to the DOMvar loadTime = document.createElement('div');loadTime.textContent = 'You loaded this page on: ' + new Date();loadTime.style.color = 'blue';document.body.appendChild(loadTime);</script></body> </html>
可以看見domContentLoaded事件的時間和異步加載外部js文件例子中個domContentLoadedNotice時間差不多。
將css和js文件內容直接書寫在html文件內部,雖然會讓html變得非常龐大,但是可以減少外部資源的加載。
優化模式
最簡單的html就是沒有css和js亦或依他資源,只有html內容。
渲染這種頁面,瀏覽器你需要初始化請求,等待html文檔加載完成,解析它,建立DOM樹,最后渲染到屏幕上:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>Critical Path: No Style</title></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div></body> </html>
?
?T0?和T1?之間的時間捕捉了網絡和服務器操作的時間。
?在理想情況下(如果HTML文檔足夠小),所有我們需要做的就是使用一個網絡往返來獲取整個文件——根據TCP傳輸協議,文件越大需要越多的網絡往返。所以上圖是建立在最理想的狀況下,加載文件只要一個網絡往返。
讓我們增加css文件:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet"></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div></body> </html>
我們使用了一個網絡往返來獲取HTML資源,在將內容最終渲染到屏幕上之前我們必須要加載css文件,所以我們使用了一個網絡往返來獲取css文件,最后渲染頁面。注意我們這里每次獲取資源使用一個網絡往返是建立在資源非常小的情況下,也就是說最理想的狀態下。
讓我們使用關鍵術語來描述渲染過程:
- 關鍵資源:?資源可能會阻塞頁面的渲染(這是使用可能是因為圖片等資源是不會阻塞頁面的渲染的,只有css和js資源會)
- 渲染路徑長度:?網絡往返的次數,亦或獲取所有資源的總時間 number of roundtrips, or the total time required to fetch all of the critical resources.
- 關鍵字節:?頁面第一次渲染時所需要的字節數,也就是將關鍵資源文件大小的總和。
- 我們上面第一個最簡單的網頁形式的例子,就包含了一個關鍵資源(HTML文檔),關鍵路徑長度是1個網絡往返(假設文件非常小),總共的關鍵字節是HTML文檔的字節大小。
讓我們比較上面HTML+CSS文件的渲染過程:
- 2?關鍵文件:CSS文件和HTML文檔
- 2?或者更多的路徑長度:加載css和HTML時所分別需要一個網絡往返?
- 9?KB 的關鍵字節:2個關鍵文件的總和
現在讓我們加入js:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet"></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script src="app.js"></script></body> </html>
過程是,加載完了HTML之后,同時加載css和js文件,因為js要查詢CSSOM,所以必須等到cssom建立完成知乎才可以運行js,之后建立DOM,最后渲染頁面。
- 3?個關鍵文件:CSS和JS和HTML文件分別一個
- 2?或者更多的路徑長度:因為加載css和js是同時進行的,也就是說在同一個網絡往返中,加載HTML進行了一次網絡往返
- 11?KB 的關鍵字節:3個關鍵文件的大小綜合
異步加載js文件:
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet"></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script src="app.js" async></script></body> </html>
?
?
異步加載js的好處:
- js腳本不會讓瀏覽器在解析其他文件時對其進行阻塞,也不會影響關鍵渲染進程
- 因為關鍵文件中不再包含js腳本文件,css同樣不需要阻塞domContentLoaded事件
- domContentLoaded事件越快被觸發,其他的應用邏輯也會越快被執行
所以,最后的結果是我們只有兩個關鍵文件,且我們只有兩個路徑長度(因為我們只進行了兩次網路往返),并且9kb的關鍵字節。
?
如果css只是針對打印機設備呢?
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><link href="style.css" rel="stylesheet" media="print"></head><body><p>Hello <span>web performance</span> students!</p><div><img src="awesome-photo.jpg"></div><script src="app.js" async></script></body> </html>
因為css資源只是用于打印機設備,所以瀏覽器不需要下載css時進行渲染阻塞,所以,一旦DOM構造完成,瀏覽器就有足夠的信息來渲染頁面!
所以,文檔只有一個關鍵文件(HTML文檔),最小的渲染路徑(只有一個網絡往返)。
?
原文:https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp?hl=en