簡單渲染過程
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建構和頁面呈現。使用者可以在所有腳本完成加載之前看到頁面
兩者的差別就是他們将在那一刻執行腳本,在這之前我們需要了解浏覽器為其加載的每個網頁追蹤細粒度時間戳
-
: 浏覽器即将開始解析第一批收到的HTML文檔位元組domLoading
-
: 表示浏覽器完成對所有HTML的解析并且DOM建構完成的時間點domInteractive
-
: 表示DOM準備就緒并且沒有樣式表阻止JavaScript執行的時間點domContentLoaded
-
: 所有處理完成,并且網頁上的所有資源都已經下載下傳完畢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,預先下載下傳相關資源。
- 浏覽器遇到
且沒有defer或async屬性的 标簽時,會觸發頁面渲染,因而如果前面CSS資源尚未加載完畢時,浏覽器會等待它加載完畢在執行腳本。<script>
渲染優化
- HTML文檔結構層次盡量少,最好不深于六層;
- 腳本盡量後放,放在前即可;
- 少量首屏樣式内聯放在标簽内;
- 樣式結構層次盡量簡單;
- 在腳本中盡量減少DOM操作,盡量緩存通路DOM的樣式資訊,避免過度觸發回流;
- 減少通過JavaScript代碼修改元素樣式,盡量使用修改class名方式操作樣式或動畫;
- 動畫盡量使用在絕對定位或固定定位的元素上;
- 隐藏在螢幕外,或在頁面滾動時,盡量停止動畫;
- 盡量緩存DOM查找,查找器盡量簡潔;
- 涉及多域名的網站,可以開啟域名預解析
參考
- https://www.cnblogs.com/CandyManPing/p/6635008.html
- http://blog.codingplayboy.com/2017/03/29/webpage_render/
作者:Jay_Gao
連結:https://juejin.im/post/5b2e352ef265da59af409832
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。