的想法:如果我要構建快速可靠的網(wǎng)站,需要真正了解瀏覽器渲染網(wǎng)頁的每個步驟機制,這樣就可以在開發(fā)過程中對每個步驟進行優(yōu)化。這篇文章是我在較高水平上對端到端過程的學習總結。
好了,廢話不多說,我們開始吧。這個過程可以分為以下幾個主要階段:
當瀏覽器通過網(wǎng)絡接收頁面的HTML數(shù)據(jù)時,它會立即設置解析器將HTML轉(zhuǎn)換為文檔對象模型(DOM)。
文檔對象模型 (DOM) 是HTML和XML文檔的編程接口。它提供了對文檔的結構化的表述,并定義了一種方式可以使從程序中對該結構進行訪問,從而改變文檔的結構,樣式和內(nèi)容。DOM 將文檔解析為一個由節(jié)點和對象(包含屬性和方法的對象)組成的結構集合。簡言之,它會將web頁面和腳本或程序語言連接起來。
解析過程的第一步是將HTML分解并表示為開始標記、結束標記及其內(nèi)容標記,然后它可以構造DOM。
當解析器遇到外部資源(如CSS或JavaScript文件)時,解析器將提取這些文件。解析器在加載CSS文件時繼續(xù)運行,此時會阻止頁面渲染,直到資源加載解析完(稍后會詳細介紹)。
JavaScript 文件略有不同-默認情況下,解析器會在加載 JS 文件然后進行解析同時會阻止對HTML的解析。可以將兩個屬性添加到腳本標簽中以減輕這種情況:defer 和async。兩者都允許解析器在后臺加載JavaScript 文件的同時繼續(xù)運行,但是它們的執(zhí)行方式不同。關于這一點后面還會再講一點,但總的來說:
defer表示文件的執(zhí)行將被延遲,直到文檔的解析完成為止。如果多個文件具有defer屬性,則將按照頁面放置的順序依次執(zhí)行。
<script type="text/javascript" src="script.js" defer>
async 意味著文件將在加載后立即執(zhí)行,這可能是在解析過程中或在解析過程之后執(zhí)行的,因此不能保證異步腳本的執(zhí)行順序。
<script type="text/javascript" src="script.js" async>
<link> 元素的 rel 屬性的屬性值preload能夠讓你在你的HTML頁面中 <head>元素內(nèi)部書寫一些聲明式的資源獲取請求,可以指明哪些資源是在頁面加載完成后即刻需要的。
對于這種即刻需要的資源,你可能希望在頁面加載的生命周期的早期階段就開始獲取,在瀏覽器的主渲染機制介入前就進行預加載。這一機制使得資源可以更早的得到加載并可用,且更不易阻塞頁面的初步渲染,進而提升性能。
<link href="style.css" rel="preload" as="style" />
你可能很早就知道DOM,但對**CSSOM(CSS對象模型)**可能聽得少,反正我也沒聽過幾次。
CSS 對象模型 (CSSOM) 是樹形形式的所有CSS選擇器和每個選擇器的相關屬性的映射,具有樹的根節(jié)點,同級,后代,子級和其他關系。CSSOM 與 文檔對象模型(DOM) 非常相似。兩者都是關鍵渲染路徑的一部分,也是正確渲染一個網(wǎng)站必須采取的一系列步驟。
CSSOM 與 DOM一起構建渲染樹,瀏覽器依次使用渲染樹來布局和繪制網(wǎng)頁。
與HTML文件和DOM相似,加載CSS文件時,必須將它們解析并轉(zhuǎn)換為樹-這次是CSSOM。它描述了頁面上的所有CSS選擇器,它們的層次結構和屬性。
CSSOM 與 DOM的不同之處在于它不能以增量方式構建,因為CSS規(guī)則由于特定性而可以在各個不同的點相互覆蓋。這就是CSS 阻塞渲染的原因,因為在解析所有CSS并構建CSSOM之前,瀏覽器無法知道每個元素在屏幕上的位置。
不同的瀏覽器有不同的 JS 引擎來執(zhí)行此任務。從計算機資源的角度來看,解析 JS 可能是一個昂貴的過程,比其他類型的資源更昂貴,因此優(yōu)化它對于獲得良好的性能是如此重要。
加載的JS和DOM被完全解析并準備就緒后就會 emit document.DOMContentLoaded事件。對于需要訪問DOM的任何腳本,例如以某種方式進行操作或偵聽用戶交互事件,優(yōu)良作法是在執(zhí)行腳本之前先等待此事件。
document.addEventListener('DOMContentLoaded', (event) => {
// 這里面可以安全地訪問DOM了
});
在所有其他內(nèi)容(例如異步JavaScript,圖像等)完成加載后,將觸發(fā)window.load事件。
window.addEventListener('load', (event) => {
// 頁面現(xiàn)已完全加載
});
渲染樹是DOM和CSSOM的組合,表示將要渲染到頁面上的所有內(nèi)容。這并不一定意味著渲染樹中的所有節(jié)點都將在視覺上呈現(xiàn),例如,將包含opacity: 0或visibility: hidden的樣式的節(jié)點,并仍然可以被屏幕閱讀器等讀取,而display: none不包括任何內(nèi)容。此外,諸如<head>之類的不包含任何視覺信息的標簽將始終被忽略。
與 JS 引擎一樣,不同的瀏覽器具有不同的渲染引擎。
現(xiàn)在我們有了完整的渲染樹,瀏覽器知道了要渲染什么,但是不知道在哪里渲染。因此,必須計算頁面的布局(即每個節(jié)點的位置和大小)。渲染引擎從頂部開始一直向下遍歷渲染樹,計算應顯示每個節(jié)點的坐標。
完成之后,最后一步是獲取布局信息并將像素繪制到屏幕上。
作者:James Starkie 譯者:前端小智 來源:dev
原文:https://dev.to/jstarmx/how-the-browser-renders-a-web-page-1ah
要:在本文中,將重點關注網(wǎng)頁的初始渲染,即它從解析 HTML 開始。 我將探索可能導致高渲染時間的問題,以及如何解決它們。
本文分享自華為云社區(qū)《頁面首屏渲染性能指南-云社區(qū)-華為云》,作者:Ocean2022。
我們知道渲染頁面是一個將服務器的響應內(nèi)容翻譯成圖片的過程。但是,如果你頁面的渲染性能比較糟糕的話,可能會帶來相對較高的跳出率。
在本文中,我將重點關注網(wǎng)頁的初始渲染,即它從解析 HTML 開始。 我將探索可能導致高渲染時間的問題,以及如何解決它們。
關鍵渲染路徑 (CRP) 是瀏覽器將代碼轉(zhuǎn)換為屏幕上可顯示像素的過程。 它有幾個階段,其中一些可以并行執(zhí)行以節(jié)省時間,但有些部分必須依次完成。 如下圖所示:
首先,一旦瀏覽器得到響應,它就會開始解析它。 當它遇到依賴項時,它會嘗試下載它。 如果它是一個樣式表文件,瀏覽器必須在渲染頁面之前完全解析它,這就是為什么 CSS 會阻塞渲染的原因。
如果是腳本,瀏覽器必須:停止解析,下載腳本,然后運行。 只有在那之后它才能繼續(xù)解析,因為 JavaScript 程序可以改變網(wǎng)頁的內(nèi)容(尤其是 HTML)。 這就是為什么 JS 會阻塞解析的原因。
完成所有解析后,瀏覽器將構建文檔對象模型 (DOM) 和級聯(lián)樣式表對象模型 (CSSOM)。 將它們組合在一起得到渲染樹。 頁面的不顯示部分不會進入渲染樹,因為它只包含繪制頁面所需的數(shù)據(jù)。
倒數(shù)第二步是將渲染樹進行布局, 這個階段也稱為回流:就是計算每個渲染樹節(jié)點的每個位置及其大小的地方。
最后一步是繪制。 它會根據(jù)瀏覽器在前一階段計算得到的數(shù)據(jù)對像素進行著色。
因此,根據(jù)這一過程,我們在優(yōu)化性能方面,得出了一些結論。如果你要提升頁面初始化渲染的性能,你需要:
同時,我們會根據(jù)下面 3 個指標來衡量優(yōu)化的效率:
除了渲染時間之外,還有其他一些因素也需要考慮。例如,你的頁面使用了多少阻塞資源以及下載它們需要多長時間。
鑒于我們在上面得出的結論,我們得出網(wǎng)站性能優(yōu)化有三種主要策略:
首先,移除所有未使用的部分,例如 JavaScript 中無法訪問的函數(shù)、帶有從不匹配任何元素的選擇器的樣式以及被 CSS 永遠隱藏的 HTML 標簽。 其次,刪除所有重復項。
然后,我建議建立一個自動壓縮過程。 例如,它應該從你的后端服務中刪除所有注釋(但不是源代碼)以及每個不包含附加信息的字符(例如 JS 中的空白字符)。
完成后,我們剩下的可以是文本字符串。 這意味著我們可以安全地應用諸如 GZIP(大多數(shù)瀏覽器都理解)之類的壓縮算法。
最后,還有緩存。 瀏覽器第一次呈現(xiàn)頁面時它不會有幫助,但它會在以后的訪問中節(jié)省很多。 但是,記住兩點至關重要:
當然,應該為每個資源定義緩存策略。 有些可能很少改變或根本不會改變,有的則是變化的很快,還有些文件包含敏感的信息(可以使用 “private” 防止 CDN 緩存私有數(shù)據(jù))。
“關鍵”僅指網(wǎng)頁正確呈現(xiàn)所需的資源。 因此,我們可以直接跳過所有流程中沒有涉及的樣式以及腳本文件。
為了告訴瀏覽器不需要特定的 CSS 文件,我們應該為所有引用樣式表的鏈接設置媒體屬性。 使用這種方法,瀏覽器將只根據(jù)需要處理與當前媒體(設備類型、屏幕尺寸)匹配的資源,同時降低所有其他樣式表的優(yōu)先級。 例如,如果你將 media=“print” 屬性添加到引用樣式以打印頁面的樣式標記,則這些樣式不會在不打印媒體時干擾你的關鍵渲染路徑。
為了進一步改進該過程,你還可以將一些樣式內(nèi)聯(lián),這可以為我們節(jié)省了至少一次到服務器的往返行程。
如上所述,腳本會阻塞解析,因為它們可以改變 DOM 和 CSSOM。 為了避免這一點,所有腳本標簽都必須用屬性標記——異步或延遲。
標有 async 的腳本不會阻塞 DOM 構建或 CSSOM,因為它們可以在 CSSOM 構建之前執(zhí)行。 但請記住,內(nèi)聯(lián)腳本無論如何都會阻止 CSSOM,除非你將它們放在 CSS 之上。
相比之下,標有 defer 的腳本將在頁面加載結束時進行執(zhí)行。
換句話說,使用 defer,腳本直到頁面加載事件被觸發(fā)后才會執(zhí)行,而 async 讓腳本在文檔被解析時就會在后臺運行。
最后,應將 CRP 長度縮短到可能的最小值。
作為樣式標簽屬性的媒體查詢將減少必須下載的資源總數(shù)。 script 標簽屬性 defer 和 async 將防止相應的腳本阻塞解析。
使用 GZIP 壓縮、壓縮和歸檔資源將減少傳輸數(shù)據(jù)的大小(從而也減少數(shù)據(jù)傳輸時間)。
內(nèi)聯(lián)一些樣式和腳本也可以減少瀏覽器和服務器之間的往返次數(shù)。
按照最新的最佳性能實踐理念,一個網(wǎng)站應該做的最快的第一件事就是展示 ATF 內(nèi)容。 ATF 代表首屏。 這是立即可見的區(qū)域,無需滾動。 因此,最好以首先加載所需樣式和腳本的方式重新排列與渲染相關的所有內(nèi)容,而其他所有內(nèi)容都停止(既不解析也不渲染)。
總而言之,網(wǎng)站性能優(yōu)化包含了網(wǎng)站響應的各個方面,例如緩存、設置 CDN、重構、資源優(yōu)化等,但是所有這些都可以逐步完成。 作為 Web 開發(fā)人員,你可以將本文作為參考,并始終記住在實驗之前和之后測量性能。
瀏覽器開發(fā)人員盡最大努力優(yōu)化你訪問的每個頁面的網(wǎng)站性能,這就是瀏覽器通常實現(xiàn)所謂的“預加載器”的原因。 這部分程序會在你以 HTML 格式請求的資源之前進行掃描,以便一次發(fā)出多個請求并讓它們并行運行。 這就是為什么在 HTML(逐行)以及腳本標簽中保持樣式標簽彼此靠近的原因。
此外,嘗試批量更新 HTML 以避免多個布局事件,這些事件不僅由 DOM 或 CSSOM 中的更改觸發(fā),而且在設備方向更改和窗口大小調(diào)整時也會觸發(fā)。
點擊下方,第一時間了解華為云新鮮技術~
華為云博客_大數(shù)據(jù)博客_AI博客_云計算博客_開發(fā)者中心-華為云
者:Jartto
轉(zhuǎn)載鏈接:http://jartto.wang/2019/10/23/css-theory-and-optimization/
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。