網頁的加載性能是影響用户體驗的最重要因素,頁面加載時間過長,極有可能會令用户直接關閉網頁,即使網頁本身的流程和UI等方面優化得再出色,也不會有任何價值。本文將以優化網頁加載性能的角度出發,介紹網頁渲染的過程以及各類資源阻塞網頁渲染的情況,並給出優化的方向。
(本文以Chrome為主瀏覽器進行討論,其他瀏覽器可能會存在細微不同的情況,不在本文討論的範圍)
網頁的渲染過程
五個步驟
想要知道如何優化網頁加載性能,首先得清楚瀏覽器加載一個網頁經過了哪些步驟,才能明白應該從哪些方面着手優化。一般來説,瀏覽器從服務器端獲取到HTML文件後就開始了加載過程,加載網頁的過程包括如下五個步驟:
- HTML代碼轉化成DOM,並並行地獲取外部資源(包括HTML中出現的JS、CSS、圖片文件)
- CSS文件下載完成,CSS代碼轉化成CSSOM(CSS Object Model)
- 結合DOM和CSSOM,生成一棵渲染樹(包含每個節點的視覺信息)
- 生成佈局(Layout):將所有渲染樹的所有節點進行平面合成,該步驟可以計算出元素在網頁中的位置
- 繪製(Paint):將網頁內容繪製在屏幕上
在加載過程中,前三步的耗時主要在於外部資源獲取上,只有CSS資源下載完成才能完成第二第三步,生成渲染樹;而後兩步Layout和Paint較為耗時,且在網頁渲染過程中會執行多次,其中首次Paint的耗時可以視為頁面從空白到有內容的時間,減小這部分用時可以給用户以網頁加載快的錯覺。
瀏覽器實踐
瀏覽器具備一些工具可以記錄頁面渲染的過程,進而幫助我們分析哪些方面的性能還有提升的空間。
以最簡單的HTML結構來熟悉一下Chrome中的Performance工具(之前的名字是timeline)。
首先我們打開控制枱,切換到Performance工具,在頁面加載前點擊左上角的圓點,開始錄製,頁面加載完再次點擊,停止錄製,等數據分析完成切換到“Event Log”就可以開始查看渲染過程了。
我們先以最簡單的結構進行分析,如加載下方index.html文件:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>測試瀏覽器渲染</title>
<link rel="stylesheet" href="./assets/style.css">
</head>
<body>
<h1>koa by html file</h1>
<script src="./assets/script.js"></script>
<img width="300" src="./assets/image.png">
</body>
</html>
分析這個網頁加載情況,如下截圖所示:
(上圖為渲染五個步驟中的第一步,接收html文件解析並並行請求外部資源)
(上圖為2, 3和4, 5步,接收到CSS文件後構建渲染樹,進行Layout和首次的Paint)
(上圖為Load事件之後的Paint)
接着我們修改一下node層代碼,令CSS文件返回延遲5s,會得到如下“Event Log”
(css文件延遲5s後,會發現首次Layout和Paint都延遲到5s之後)
為了研究JS文件對首次Paint的影響,我們令JS文件返回延遲5s,CSS和圖片都即時返回,會得到如下“Event Log”
從上面的分析可以看出:
- 外部資源是在開頭並行獲取的,並非HTML解析到引用行再發請求的;
- CSS文件加載的速度是影響首次Paint時間的因素,當CSS文件下載完成後才能進行CSSOM和渲染樹的構建,然後才是Layout和Paint過程;
- JS文件加載的速度不影響首次Paint時間,即使JS文件本身有修改DOM節點的操作,也只是在JS文件加載完成後再進行一次Layout和Paint;
各類資源阻塞網頁加載的情況
阻塞網頁加載的情況,可以分為兩種:一種是阻塞HTML解析,即阻塞Event Log中的parse HTML步驟,令HTML結構停止解析;另一種是阻塞渲染,即阻塞Event Log中的Paint步驟,令已經解析好的HTML結構也無法顯示到瀏覽器上。
CSS資源
CSS樣式無法影響DOM結構,但能改變頁面中元素呈現的模樣,所以CSS資源可以阻塞渲染,但不會阻塞HTML解析。
為了研究CSS是否會阻塞渲染和解析,我們依然使用之前的index.html文件,只將style.css延遲5s返回,得到如下實驗結果:
(解析完HTML之後,就並行發JS、CSS和圖片請求,JS和圖片先接收完成,但沒有觸發首次Paint渲染,而是延遲到css文件接收完成,CSSOM和渲染樹構建完成才觸發了首次Paint,因此CSS資源會阻塞渲染)
相同情況下,頁面的展示和HTML結構如何呢?請看下方動圖:
(在CSS文件接收完成之前,雖然HTML結構已經解析完成,即Elements中HTML結構已經被解析出來,但頁面是一片空白,説明CSS資源不會阻塞解析,但會阻塞渲染)
JS資源
JS腳本會影響DOM結構,也能改變頁面中元素呈現的模樣,所以JS資源既能阻塞渲染又能阻塞HTML解析。
為了研究JS是否會阻塞渲染和解析,我們依然使用index.html文件,只將script.js延遲5s返回,得到如下實驗結果:
(並行發送請求,CSS和圖片資源先加載完,觸發了首次Paint渲染,此時JS文件還沒開始下載,所以JS文件並不會影響頁面的首次渲染)
(延遲了5s後,開始接收JS文件,完成後,再次Paint渲染)
相同情況下,頁面的展示和HTML結構,請看下方動圖:
(JS開始下載之前,CSS資源已經下載完成,頁面也解析並渲染出一部分內容,這部分是JS文件由script標籤引入之前的內容,而script標籤之後的圖片則需等到JS文件加載完成後才進行解析並渲染出來,因此JS資源會阻塞之後的HTML結構解析和渲染)
圖片資源
圖片資源無法對其他DOM節點進行操作,所以不會阻塞渲染和HTML解析。
為了研究圖片資源是否會阻塞渲染和解析,我們依然使用index.html文件,只將png文件延遲5s返回,得到如下實驗結果:
(並行發送請求,CSS和JS文件都加載後,完成HTML解析和首次Paint渲染)
(當圖片文件加載完成後,再次渲染)
相同情況下,頁面的展示和HTML結構,請看下方動圖:
(圖片加載之前,頁面的HTML結構已經解析完成,並且畫面也出現在屏幕上,説明圖片資源不會阻塞HTML的解析和畫面渲染)
針對首屏的優化建議
從上面的實驗可以看出,三種資源中只有CSS文件會影響首次Paint的時間。無論是延遲JS還是圖片,在CSS文件加載完成後,都能先渲染出已經解析好的HTML內容。由於在首次Paint之前,頁面是一片空白,所以首次Paint的時間也就相當於首屏時間。
針對首次Paint時間的優化,我們可以從以下角度進行考慮:
- CSS會影響首次Paint的時間,應該儘快加載,因此將其放在head標籤處以保障能在解析完HTML後發出請求;
- JS不會影響首屏的時間,但由於JS會阻塞之後的頁面內容的解析與渲染,要避免JS在首屏位置出現,一般將它們放在body的最後,另外,JS會影響整體頁面加載的性能,可以使用defer和async轉成非阻塞模式(見參考文獻[6]);
- 圖片不影響首屏時間,但會影響Load事件的時機(沒有找到合適的參考文獻,待我另起一篇文章補充);
參考文獻
- 《js一定要放在底部嗎?》https://segmentfault.com/a/11...
- 《聊聊瀏覽器的渲染機制》https://www.jianshu.com/p/c90...
- 《網頁性能管理詳解》http://www.ruanyifeng.com/blo...
- 《瀏覽器渲染過程與性能優化》https://sylvanassun.github.io...
- 《How browsers work》http://taligarsiel.com/Projec...
- 《JavaScript阻塞剖析與改善》http://www.cnblogs.com/giggle...