簡單渲染過程
url解析:
-
使用者輸入URL地址
-
瀏覽器解析URL解析出主機名
-
瀏覽器將主機名轉換成伺服器ip地址(瀏覽器先查詢本地DNS快取列表 沒有的話 再向瀏覽器預設的DNS伺服器傳送查詢請求 同時快取)
-
瀏覽器將埠號從URL中解析出來
-
瀏覽器建立一條與目標Web伺服器的TCP連線(三次握手)
-
瀏覽器向伺服器傳送一條HTTP請求報文
-
伺服器向瀏覽器返回一條HTTP響應報文
-
關閉連線 瀏覽器解析文件
-
如果文件中有資源 重複6 7 8 動作 直至資源全部載入完畢
html解析:
- 將HTML構建成一個DOM樹(DOM = Document Object Model 文件物件模型),DOM 樹的構建過程是一個深度遍歷過程:當前節點的所有子節點都構建好後才會去構建當前節點的下一個兄弟節點。
- 將CSS解析成CSS去構造CSS Rule Tree
- 根據DOM樹和CSSOM來構造 Rendering Tree(渲染樹)。注意:Rendering Tree 渲染樹並不等同於 DOM 樹,因為一些像 Header 或 display:none 的東西就沒必要放在渲染樹中了。
- 有了Render Tree,瀏覽器已經能知道網頁中有哪些節點、各個節點的CSS定義以及他們的從屬關係。
- 下一步操作稱之為Layout,顧名思義就是計算出每個節點在螢幕中的位置 layout render tree。
- 再下一步就是繪製(Paint),即遍歷render樹,並使用瀏覽器UI後端層繪製每個節點。
Render Tree
渲染樹,代表一個文件的視覺展示,瀏覽器通過它將文件內容繪製在瀏覽器視窗,展示給使用者,它由按順序展示在螢幕上的一系列矩形物件組成,這些矩形物件都帶有字型,顏色和尺寸,位置等視覺樣式屬性。對於這些矩物件,FireFox稱之為框架(frame),Webkit瀏覽器稱之為渲染物件(render object, renderer),後文統稱為渲染物件。
每一個渲染物件都代表著其對應DOM節點的CSS盒子,該盒子包含了尺寸,位置等幾何資訊,同時它指向一個樣式物件包含其他視覺樣式資訊。
每一個渲染物件都對應著DOM節點,但是非視覺(隱藏,不佔位)DOM元素不會插入渲染樹,如<head>
元素或宣告display: none;
的元素,渲染物件與DOM節點不是簡單的一對一的關係,一個DOM可以對應一個渲染物件,但一個DOM元素也可能對應多個渲染物件,因為有很多元素不止包含一個CSS盒子,如當文字被折行時,會產生多個行盒,這些行會生成多個渲染物件;又如行內元素同時包含塊元素和行內元素,則會建立一個匿名塊級盒包含內部行內元素,此時一個DOM對應多個矩形物件(渲染物件)。
Layout
建立渲染樹後,下一步就是佈局(Layout),或者叫回流(reflow,relayout),這個過程就是通過渲染樹中渲染物件的資訊,計算出每一個渲染物件的位置和尺寸,將其安置在瀏覽器視窗的正確位置,而有些時候我們會在文件佈局完成後對DOM進行修改,這時候可能需要重新進行佈局,也可稱其為迴流,本質上還是一個佈局的過程,每一個渲染物件都有一個佈局或者回流方法,實現其佈局或迴流。
Paint
最後是繪製(paint)階段或重繪(repaint)階段,瀏覽器UI元件將遍歷渲染樹並呼叫渲染物件的繪製(paint)方法,將內容展現在螢幕上,也有可能在之後對DOM進行修改,需要重新繪製渲染物件,也就是重繪,繪製和重繪的關係可以參考佈局和迴流的關係。
Reflow (迴流/重排)
當它發現了某個部分發生了變化影響了佈局,渲染樹需要重新計算。
原因:
- DOM操作,如增加,刪除,修改或移動;
- 變更內容;
- 啟用偽類;
- 訪問或改變某些CSS屬性(包括修改樣式表或元素類名或使用JavaScript操作等方式);
- 瀏覽器視窗變化(滾動或尺寸變化)
如何減少reflow
- 儘可能限制reflow的影響範圍。需要改變元素的樣式,不要通過父級元素影響子元素。最好直接加在子元素上。
- 通過設定style屬性改變結點樣式的話,每設定一次都會導致一次reflow。所以最好通過設定class的方式。
- 減少不必要的DOM層級(DOM depth)。改變DOM樹中的一級會導致所有層級的改變,上至根部,下至被改變節點的子節點。這導致大量時間耗費在執行reflow上面。
- 避免不必要的複雜的CSS選擇器,尤其是後代選擇器(descendant selectors),因為為了匹配選擇器將耗費更多的CPU。
Repaint(重繪)
改變了某個元素的背景顏色,文字顏色等,不影響元素周圍或內部佈局的屬性,將只會引起瀏覽器的repaint,根據元素的新屬性重新繪製,使元素呈現新的外觀。重繪不會帶來重新佈局,並不一定伴隨重排; Reflow要比Repaint更花費時間,也就更影響效能。所以在寫程式碼的時候,要儘量避免過多的Reflow。
script解析
或許是由於通常會在JavaScript指令碼中改變文件DOM結構,於是瀏覽器以同步方式解析,載入和執行指令碼,瀏覽器在解析文件時,當解析到<script>
標籤時,會解析其中的指令碼(對於外鏈的JavaScript檔案,需要先載入該檔案內容,再進行解析),然後立即執行,這整個過程都會阻塞文件解析,直到指令碼執行完才會繼續解析文件。就是說由於指令碼是同步載入和執行的,它會阻塞文件解析,這也解釋了為什麼現在通常建議將<script>
標籤放在</body>
標籤前面,而不是放在<head>
標籤裡。現在HTML5提供defer和async兩個屬性支援延遲和非同步載入JavaScript檔案
defer和async
當然我們可以通過設定defer
和async
屬性來非同步載入不太重要的指令碼
這兩個屬性都告訴瀏覽器,他可能會在後臺載入指令碼時繼續解析HTML,然後再在載入後執行指令碼,這樣指令碼下載不會阻止DOM構建和頁面呈現。使用者可以在所有指令碼完成載入之前看到頁面
兩者的區別就是他們將在那一刻執行指令碼,在這之前我們需要了解瀏覽器為其載入的每個網頁追蹤細粒度時間戳
domLoading
: 瀏覽器即將開始解析第一批收到的HTML文件位元組domInteractive
: 表示瀏覽器完成對所有HTML的解析並且DOM構建完成的時間點domContentLoaded
: 表示DOM準備就緒並且沒有樣式表阻止JavaScript執行的時間點domComplete
: 所有處理完成,並且網頁上的所有資源都已經下載完畢loadEvent
: 作為每個網頁載入的最後一步,瀏覽器會觸發onload
事件,以便觸發額外的應用邏輯
defer
的執行將在domInteractive
完成之後,domContentLoaded
之前開始,他保證指令碼將按照他們在HTML中出現的順序執行,並且不會阻塞解析器
async
指令碼在完成下載之後和視窗load
事件之前的某一個時間點執行,這意味著非同步指令碼可能不按他們在HTML中出現的順序執行,這意味著他們可能會阻止DOM構建
defer
<script src="app1.js" defer></script>
<script src="app2.js" defer></script>
<script src="app3.js" defer></script>
複製程式碼
defer 屬性表示延遲執行引入的 JavaScript,即這段 JavaScript 載入時 HTML 並未停止解析,這兩個過程是並行的。整個 document 解析完畢且 defer-script 也載入完成之後(這兩件事情的順序無關),會執行所有由 defer-script 載入的 JavaScript 程式碼,然後觸發 DOMContentLoaded 事件。
defer 不會改變 script 中程式碼的執行順序,示例程式碼會按照 1、2、3 的順序執行。所以,defer 與相比普通 script,有兩點區別:載入 JavaScript 檔案時不阻塞 HTML 的解析,執行階段被放到 HTML 標籤解析完成之後。
async
<script src="app.js" async></script>
<script src="ad.js" async></script>
<script src="statistics.js" async></script>
複製程式碼
async 屬性表示非同步執行引入的 JavaScript,與 defer 的區別在於,如果已經載入好,就會開始執行——無論此刻是 HTML 解析階段還是 DOMContentLoaded 觸發之後。需要注意的是,這種方式載入的 JavaScript 依然會阻塞 load 事件。換句話說,async-script 可能在 DOMContentLoaded 觸發之前或之後執行,但一定在 load 觸發之前執行。
從上一段也能推出,多個 async-script 的執行順序是不確定的。值得注意的是,向 document 動態新增 script 標籤時,async 屬性預設是 true
CSS和JS阻塞規則
- CSS 不會阻塞 DOM 的解析,但會阻塞 DOM 渲染。
- JS 阻塞 DOM 解析,但瀏覽器會"偷看"DOM,預先下載相關資源。
- 瀏覽器遇到
<script>
且沒有defer或async屬性的 標籤時,會觸發頁面渲染,因而如果前面CSS資源尚未載入完畢時,瀏覽器會等待它載入完畢在執行指令碼。
渲染優化
- HTML文件結構層次儘量少,最好不深於六層;
- 指令碼儘量後放,放在前即可;
- 少量首屏樣式內聯放在標籤內;
- 樣式結構層次儘量簡單;
- 在指令碼中儘量減少DOM操作,儘量快取訪問DOM的樣式資訊,避免過度觸發迴流;
- 減少通過JavaScript程式碼修改元素樣式,儘量使用修改class名方式操作樣式或動畫;
- 動畫儘量使用在絕對定位或固定定位的元素上;
- 隱藏在螢幕外,或在頁面滾動時,儘量停止動畫;
- 儘量快取DOM查詢,查詢器儘量簡潔;
- 涉及多域名的網站,可以開啟域名預解析
參考
- https://www.cnblogs.com/CandyManPing/p/6635008.html
- http://blog.codingplayboy.com/2017/03/29/webpage_render/