優化關乎速度和滿意度。
- 從使用者體驗(UX)角度,我們希望前端網頁可以快速載入
- 從開發體驗(DX)角度,我們希望前端是快速,簡潔,規範的
瀏覽器都做了什麼
我們希望瀏覽器開啟一個簡單的網頁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!DOCTYPE html> <html> <head> <title>The "Click the button" page</title> <meta charset="UTF-8"> <link rel="stylesheet" href="styles.css" /> </head> <body> <h1> Click the button. </h1> <button type="button">Click me</button> <script> var button = document.querySelector("button"); button.style.fontWeight = "bold"; button.addEventListener("click", function () { alert("Well done."); }); </script> </body> </html> |
瀏覽器如何渲染網頁
- 使用 HTML 建立文件物件模型(DOM)
- 使用 CSS 建立 CSS 物件模型(CSSOM)
- 基於 DOM 和 CSSOM 執行指令碼(Scripts)
- 合併 DOM 和 CSSOM 形成渲染樹(Render Tree)
- 使用渲染樹佈局(Layout)所有元素
- 渲染(Paint)所有元素
步驟一 — HTML
瀏覽器從上到下讀取標籤,把他們分解成節點,從而建立 DOM 。
HTML 載入優化策略
- 樣式在頂部,指令碼在底部
總體思路是儘可能早的載入樣式,儘可能晚的載入指令碼。原因是指令碼執行之前,需要 HTML 和 CSS 解析完成,因此,樣式儘可能的往頂部放,當底部指令碼開始執行之前,樣式有足夠的時間完成計算。
進一步講講如何優化
- 最小化和壓縮
方法可用於所有內容,包括 HTML,CSS,JavaScript,圖片和其它資源。
最小化是移除所有多餘的字元,包括空格,註釋,多餘的分號,等等。
壓縮比如 GZip,大大壓縮下載檔案的大小
兩種方法都用的情況下,資源載入量減少了 80% 到 90%。比如:bootstrap 節省了 87% 的流量。
- 無障礙
不會提升頁面的下載速度,但會大大提升殘障人士的滿意度。給元素加上 aria
標籤,圖片提供 alt
文字,HTML 5 無障礙參見。
使用諸如 WAVE 的工具鑑別哪些地方可以提高可訪問性。
步驟二 — CSS
當瀏覽器發現任何與節點相關的樣式時,比如:外部,內部,或行內樣式,立即停止渲染 DOM ,並利用這些節點建立 CSSOM。這就是 CSS “渲染阻塞“ 的由來。這裡是不同型別樣式的優缺點。
1 2 3 4 5 6 7 8 9 10 |
//外部樣式 <link rel="stylesheet" href="styles.css"> // 內部樣式 <style> h1 { font-size: 18px; } </style> // 行內樣式 <button style="background-color: blue;">Click me</button> |
CSSOM 節點建立與 DOM 節點建立類似,隨後,兩者合併如下:
CSSOM 的構建會阻塞頁面的渲染,因此我們想盡早載入樣式,
CSS 載入優化策略
- 使用 media 屬性
media 屬性指定載入樣式的條件,比如:符合最大或最小解析度?還是面向螢幕閱讀器?
- 延遲載入 CSS
有些樣式,比如:首屏以下的,或者不那麼重要的,可以等待首屏最有價值的內容渲染完成再載入,可以使用指令碼等待頁面載入,然後再插入樣式。
這有兩個栗子:The future of loading CSS,Defer load CSS
- 只載入需要的樣式
使用 uncss 類似的工具,儘量移除不需要的樣式。
步驟三 — JavaScript
瀏覽器不斷構建 DOM / CSSOM 節點,直到發現外部或者行內的指令碼。
由於指令碼可能需要訪問或操作之前的 HTML 或樣式,我們必須等待它們構建完成。
因此瀏覽器必須停止解析節點,完成構建 CSSOM,執行指令碼,然後再繼續。這就是 JavaScript 被稱作“解析器阻塞”的原因。
指令碼只能等到先前的 CSS 節點構建完成。
JavaScript 載入優化策略
- 非同步載入指令碼
指令碼新增 async
屬性,可以通知瀏覽器不要阻塞其餘頁面的載入,下載指令碼處於較低的優先順序。一旦下載完成,就可以執行。
async
適用於不影響 DOM 或 CSSOM 的指令碼,對一些跟我們的程式碼無關的,不影響使用者體驗的外部指令碼尤其適用,比如:分析統計指令碼。
- 延遲載入指令碼
defer
跟 async
非常相似,不會阻塞頁面載入,但會等到 HTML 完成解析後再執行。
使用 defer 策略的 另一個好選擇,或者也可以使用 addEventListener
,瞭解更多,參加這裡。
不幸的是 async
和 defer
對於行內的指令碼不起作用,瀏覽器預設會編譯執行它們。
- 操作之前克隆節點
多次操作 DOM 時可以嘗試,首先克隆整個 DOM 節點更加高效,操作克隆後的節點,然後替換先前的節點,避免了多次重繪,降低了 CPU 和記憶體消耗,同時也避免了不必要的頁面閃爍。
需要注意,克隆的時候並沒有克隆事件監聽。
- Preload/Prefetch/Prerender/Preconnect
這些新屬性並不是所有的瀏覽器都支援。瞭解詳情可以看這裡:Prefetching, preloading, prebrowsing
步驟四 — 渲染樹(Render Tree)
一旦所有節點已被解析,DOM 和 CSSOM 準備合併,瀏覽器便會構建渲染樹。如果我們把節點想象成單詞,那麼物件模型就是句子,渲染樹便是整個頁面。
步驟五 — 佈局(Layout)
佈局階段需要確定頁面上所有元素的大小和位置。
步驟六 — 渲染(Paint)
最終的渲染階段,會真正地光柵化螢幕上的畫素,把頁面呈現給使用者。
整個過程耗時1秒或十分之一秒,我們的任務是讓它更快。
如果 JavaScript 事件改變了頁面的某部分,便會引起渲染樹的重繪,並且迫使佈局(Layout)和渲染(Paint)過程再次進行。
瀏覽器如何發起網路請求
當瀏覽器請求一個 URL,服務端會響應一些 HTML。
我們需要認識一個新術語,關鍵渲染路徑(Critical Rendering Path (CRP)),就是瀏覽器渲染頁面的步驟數,如下圖。
關鍵路徑長度
關鍵渲染路徑的度量標準是路徑長度。最理想的關鍵路徑長度是1。
如果頁面包含一些內部樣式和 JavaScript ,關鍵路徑發生以下改變。
新增兩步,構建 CSSOM和執行指令碼,因為我們的 HTML 有內部樣式和指令碼需要計算。由於沒有外部請求,我們的關鍵路徑長度沒變。
但是注意,我們的 HTML 大小增加到了 2kb,某些地方還是受了影響。
關鍵位元組數
三個度量標準之二出現了,關鍵位元組數,它用來衡量渲染頁面需要傳送多少位元組數。
如果你認為不需要外部資源,就大錯特錯了,外部資源可以被快取。
我們使用一個外部 CSS 檔案,一個外部 JavaScript 檔案,和一個外部帶 async
屬性的 JavaScript 檔案。關鍵路徑圖如下:
瀏覽器請求頁面,構建 DOM,發現外部資源後開始下載,CSS 和 JavaScript 有較高的優先順序,其它資源次之。
styles.css
和 app.js
通過另一個關鍵路徑獲取。暫時不獲取 analytics.js
,因為加了 async
屬性,瀏覽器將用另一個執行緒下載它,它處於較低優先順序,不會阻塞頁面渲染,也不影響關鍵路徑。
關鍵檔案
最後一個度量標準是關鍵檔案,瀏覽器渲染頁面需要下載的檔案總量。以上例子,HTML 檔案,CSS 和 JavaScript 檔案算關鍵檔案,async
的指令碼不算。當然是檔案越少越好。
回到關鍵路徑長度
以上例子就是最長的渲染路徑嗎?我認為渲染頁面時,我們僅需要下載 HTML,CSS 和 JavaScript 檔案,僅通過兩次伺服器往返就做到了。
HTTP1 檔案限制
我們瀏覽器的 HTTP1 協議,在同一個域名,同一次,允許下載的檔案數有最大限制,範圍從 2(老舊的瀏覽器)到 6(Edge,Chrome)。
各種瀏覽器請求檔案的最大併發數,參見Maximum concurrent connections to the same domain for browsers。
通過把一些資源存放到影子域名,可以繞過這個限制,以達到最佳優化效果。
注意:不要把關鍵的 CSS 放到根域名之外的其他域名,有些場景下會對 DNS 查詢和延遲起反作用。
HTTP2
如果網站使用了 HTTP2,並且使用者的瀏覽器也相容,則可以完全避開這個下載限制。
這裡有個 HTTP2 測試網站。
TCP 往返限制
每一次伺服器往返可以傳送的最大資料量是 14kb,包括所有 HTML,CSS 和指令碼的網路請求。
如果我們的 HTML,或者積累的資源請求超過 14kb時,需要多做一次伺服器往返。
大魔法師
我們整個 HTML 頁面可以很好的壓縮, GZip 可以壓縮到 2kb,遠低於 14kb 的限制,因此,一次伺服器往返就可以搞定。
關鍵路徑度量: 長度 1,檔案數 1,位元組數 2kb
瀏覽器發現外部資源(CSS 和 JavaScript)時,發起請求開始下載它們。首要下載的 CSS 檔案是 14kb,達到了往返傳輸的最大限制,因此增加了一條關鍵路徑。
關鍵路徑度量: 長度 2,檔案數 2,位元組數 16kb
餘下的資源低於 14kb,但是總共有 7 個資源,由於網站未啟用 HTTP2,我們的 Chrome,每一次往返僅可以下載 6 個檔案。
關鍵路徑度量: 長度 3,檔案數 8,位元組數 28kb
下載完最終檔案,並開始渲染 DOM。
關鍵路徑度量: 長度 4,檔案數 9,位元組數 30kb
基於以上的資訊和知識,發起每個連線時,就可以準確地預估頁面的效能了。
瀏覽器網路優化策略
- Pagespeed Insights
使用 Insights 鑑別效能問題,Chrome DevTools 也有個 audit
標籤。
- 充分利用 Chrome 開發者工具
這篇文章 值得一讀,幫你理解網路資源
- 在優質的環境裡開發,在艱苦的環境裡測試
開發時大可使用 1Tb SSD,32G 記憶體的 Macbook Pro ,但是效能測試時還是要到 Chrome 的 network
標籤下模擬低頻寬的情形,從而獲取有價值的資訊。
- 合併資源/檔案
基本上,每接收到一個外部 CSS 和 JavaScript 檔案,瀏覽器都會構建 CSSOM,執行指令碼。儘管幾個檔案可以在一次往返中傳送,但也浪費了瀏覽器的寶貴時間和資源,最好還是合併檔案,減少不必要的載入。
- 首屏內容使用內部樣式
內部 CSS 和 JavaScript 不需要請求外部資源,相反,外部資源又可以被快取,並保持 DOM 輕量,兩者沒有非黑即白。
但是一個非常好的論點是首屏關鍵內容使用內部樣式,可以避免請求額外的資源,節省時間做最有意義的渲染。
- 最小化/壓縮圖片
- 延遲載入圖片
- 非同步記載字型
- 是否真正需要 JavaScript / CSS?
原生 HTML 元素可以實現的行為是否用了指令碼?是否有樣式或者圖示可以行內建立的,不需要內部/外部資源?比如:行內 SVG。
- CDN
可以利用 CDN(內容分發網路)儲存資源,它會從離使用者最近,延遲最低的位置分發到使用者裝置,載入時間更快。
延伸閱讀
綜述
關鍵渲染路徑是最重要的,它使得網站優化有規律可循。需要關注3個指標:
1—關鍵位元組數
2—關鍵檔案數
3—關鍵路徑長度
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式