前面有講到當用戶在瀏覽器輸入url以後,通過一系列的過程,會最終向服務器請求到文檔數據,文檔數據請求到以後,瀏覽器會將這些數據傳給瀏覽器渲染引擎,渲染引擎開始正式工做了。javascript
首先瀏覽器接收到html文檔,就會把HTML在內存中轉換成DOM樹,HTML中的每一個tag都是DOM樹中的1個節點,根節點就是咱們經常使用的document對象。DOM樹裏包含了全部HTML標籤,包括display:none隱藏,還有用JS動態添加的元素等。在轉換的過程當中若是發現某個節點(node)上引用了CSS或者 image,就會再次向服務器請求css或image,而後繼續執行構建dom樹的轉換,而不須要等待請求的返回,當請求的css文件返回後,就會開始解析css style,瀏覽器把全部樣式(用戶定義的CSS和用戶代理)解析成樣式結構體,在解析的過程當中會去掉瀏覽器不能識別的樣式,好比IE會去掉-moz開頭的樣式,而FF會去掉_開頭的樣式。css
DOM Tree 和樣式結構體組合後構建render tree,也就是渲染樹。渲染樹和dom樹有很大的區別,render tree中每一個NODE都有本身的style,並且 render tree不包含隱藏的節點 (好比display:none的節點,還有head節點),由於這些節點不會用於呈現,並且不會影響呈現的,因此就不會包含到 render tree中。注意 visibility:hidden隱藏的元素仍是會包含到 render tree中的,由於visibility:hidden 會影響佈局(layout),會佔有空間。根據CSS2的標準,render tree中的每一個節點都稱爲Box (Box dimensions),理解頁面元素爲一個具備填充、邊距、邊框和位置的盒子。一旦render tree構建完畢後,瀏覽器就能夠根據render tree來繪製頁面了。html
注意:因爲瀏覽器的流佈局,對渲染樹的計算一般只須要遍歷一次就能夠完成。但 table及其內部元素除外,它可能須要屢次計算才能肯定好其在渲染樹中節點的屬性,一般要花3倍於同等元素的時間。這也是爲何咱們要避免使用 table作佈局的一個緣由。java
在瀏覽器進行加載時,實際上是並行加載全部資源。對於css和圖片等資源,瀏覽器加載是異步的,並不會影響到後續的加載、html解析和後續渲染。node
css阻塞渲染
由上面過程能夠看到,頁面佈局是在渲染樹構建好以後發生的,而渲染樹依賴css樣式結構體,因此CSS 被視爲阻塞渲染的資源(但不阻塞html的解析,不會阻塞dom樹的構建),這意味着瀏覽器將不會渲染任何已處理的內容,直至 CSSOM 構建完畢。jquery
由於css會阻塞渲染,因此咱們應該儘早的儘快地下載到客戶端,以便縮短首次渲染的時間。平時在開發的時候,應注意如下幾點:chrome
同時,還有如下優化點:瀏覽器
1、媒體查詢緩存
經過使用媒體查詢,咱們能夠根據特定的需求(好比顯示或打印),也能夠根據動態狀況(好比屏幕方向變化、尺寸調整事件等)定製外觀,服務器
<link href="style.css" rel="stylesheet"> <link href="print.css" rel="stylesheet" media="print"> <link href="other.css" rel="stylesheet" media="(min-width: 40em)">
看上面的代碼,
第一行,這樣的普通聲明,會阻塞渲染
第二行,這個聲明,只在打印網頁時應用,所以網頁在瀏覽器中加載時,不會阻塞渲染。
第三行,提供了由瀏覽器執行的「媒體查詢」,只有符合條件時,樣式表會生效,瀏覽器纔會阻塞渲染,直至樣式表下載並處理完畢。
2、preload
<link rel="preload" href="index_print.css" as="style" onload="this.rel='stylesheet'">
preload是resoure hint規範中定義的一個功能,顧名思義預加載,將rel改成preload後,至關於加了一個標誌位,瀏覽器解析的時候會提早創建鏈接或加載資源,作到儘早並行下載,而後在onload事件響應後將link的rel屬性改成stylesheet便可進行解析。
IE chrome firefox三者的差別
3、動態添加link
var style = document.createElement('link'); style.rel = 'stylesheet'; style.href = 'index.css'; document.head.appendChild(style);
js動態添加DOM元素link,不會阻塞渲染。
loadCSS.js,CSS preload polyfill第三方庫,原理同上
4、代碼簡練
js阻塞
js可能會操做html,css,因爲瀏覽器不瞭解腳本計劃在頁面上執行什麼操做,它會做最壞的假設並阻止解析器,也就是以前講過瀏覽器的GUI線程與js引擎線程是互斥的。因此,js會阻塞渲染
瀏覽器對於js腳本文件的加載,則會致使html解析和渲染中止,直至js腳本加載並執行完畢才繼續,可是對於後續的非js資源加載並不會中止,瀏覽器會對後續資源進行預加載。而資源加載是屬於另外單獨的線程,因此js加載並不會影響其餘非js資源的加載,是瀏覽器的機制。
總的來講就是如下幾點:
當CSS後面跟着嵌入的JS的時候,該CSS就會出現阻塞後面資源下載的狀況,由於瀏覽器會維持html中css和js的順序,樣式表必須在嵌入的JS執行前先加載、解析完。而嵌入的JS會阻塞後面的資源加載,因此就會出現CSS阻塞下載的狀況。
例以下面這段代碼,看瀏覽器是如何一步步將界面繪製出來
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <meta name="author" content="Reddy.Huang, i@0u0b.com"/> <title>瀏覽器渲染</title> <link href="./css/main.css" rel="stylesheet"> </head> <body> <div class="wrap"> <div class="left"> </div> <div class="middle"> <div class="line"> </div> </div> <div class="right"> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> </div> </div> <script src="./js/3.js"></script> </body> </html>
經過瀏覽器的工具上的能夠很清楚的看到界面的渲染過程,也能夠很清楚的看到請求加載資源的時候,不會對html解析形成影響,但若是資源加載過慢,會致使渲染阻塞,經過此圖能夠很好的理解瀏覽器的渲染機制
若是我把js放在css以後,以下代碼:
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <meta name="author" content="Reddy.Huang, i@0u0b.com"/> <title>瀏覽器渲染</title> <link href="./css/main.css" rel="stylesheet"> <script src="./js/3.js"></script> </head> <body> <div class="wrap"> <div class="left"> </div> <div class="middle"> <div class="line"> </div> </div> <div class="right"> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> </div> </div> </body> </html>
再次查看瀏覽器的渲染過程:
圖中能夠明顯的看出,首先瀏覽器開始解析html,而後再解析的過程當中遇到css,開始加載css資源,遇到js開始加載js資源,當css加載完成後,開始解析css,js加載完成後,則開始解析js,此時解析html生成dom樹會中止,直到js解析完成以後,纔再次開始解析html,從新計算樣式,佈局,生成渲染樹,最終纔是界面繪製,因此在開發的時候不要將js文件寫在頭部,這樣會影響界面的繪製,致使界面出現空白
重繪
當render tree中的一些元素須要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響佈局的,好比background-color。則就叫稱爲重繪。
迴流
當render tree中的一部分(或所有)由於元素的規模尺寸,佈局,隱藏等改變而須要從新構建。這就稱爲迴流(reflow)。
每一個頁面至少須要一次迴流,就是在頁面第一次加載的時候。在迴流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並從新構造這部分渲染樹,完成迴流後,瀏覽器會從新繪製受影響的部分到屏幕中,該過程成爲重繪。
迴流必然會形成重繪,重繪不會形成迴流。
迴流什麼時候發生:
當頁面佈局和幾何屬性改變時就須要迴流。下述狀況會發生瀏覽器迴流:
一、添加或者刪除可見的DOM元素;
二、元素位置改變;
三、元素尺寸改變——邊距、填充、邊框、寬度和高度
四、內容改變——好比文本改變或者圖片大小改變而引發的計算值寬度和高度改變;
五、頁面渲染初始化;
六、瀏覽器窗口尺寸改變——resize事件發生時;
迴流比重繪的代價要更高,迴流的花銷跟render tree有多少節點須要從新構建有關係,假設你直接操做body,好比在body最前面插入1個元素,會致使整個render tree迴流,這樣代價固然會比較高,但若是是指body後面插入1個元素,則不會影響前面元素的迴流。
若是每句JS操做都去迴流重繪的話,瀏覽器可能就會受不了。因此不少瀏覽器都會優化這些操做,瀏覽器會維護1個隊列,把全部會引發迴流、重繪的操做放入這個隊列,等隊列中的操做到了必定的數量或者到了必定的時間間隔,瀏覽器就會flush隊列,進行一個批處理。這樣就會讓屢次的迴流、重繪變成一次迴流重繪。
雖然有了瀏覽器的優化,但有時候咱們寫的一些代碼可能會強制瀏覽器提早flush隊列,這樣瀏覽器的優化可能就起不到做用了。當你請求向瀏覽器請求一些 style信息的時候,就會讓瀏覽器flush隊列,好比:
當請求上面的一些屬性的時候,瀏覽器爲了給你最精確的值,須要flush隊列,由於隊列中可能會有影響到這些值的操做。即便你獲取元素的佈局和樣式信息跟最近發生或改變的佈局信息無關,瀏覽器都會強行刷新渲染隊列。
由於迴流的開銷很大,因此咱們在寫代碼的時候,有不少須要注意的地方:
// 很差的寫法 var left = 1; var top = 1; el.style.left = left + "px"; el.style.top = top + "px"; // 比較好的寫法 el.className += " className1"; // 比較好的寫法 el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
a、使用documentFragment或div等元素進行緩存操做,這個主要用於添加元素的時候,你們應該都用過,就是先把全部要添加到元素添加到1個div(這個div也是新加的),最後才把這個div append到body中。
b、先display:none 隱藏元素,而後對該元素進行全部的操做,最後再顯示該元素。因對display:none的元素進行操做不會引發迴流、重繪。因此只要操做只會有2次迴流。
// 別這樣寫 for(循環) { elel.style.left = el.offsetLeft + 5 + "px"; elel.style.top = el.offsetTop + 5 + "px"; } // 這樣寫好點 var left = el.offsetLeft,top = el.offsetTop,s = el.style; for(循環) { left += 10; top += 10; s.left = left + "px"; s.top = top + "px"; }
// block1是position:absolute 定位的元素,它移動會影響到它父元素下的全部子元素。 // 由於在它移動過程當中,全部子元素須要判斷block1的z-index是否在本身的上面, // 若是是在本身的上面,則須要重繪,這裏不會引發迴流 $("#block1").animate({left:50}); // block2是相對定位的元素,這個影響的元素與block1同樣,可是由於block2非絕對定位 // 並且改變的是marginLeft屬性,因此這裏每次改變不但會影響重繪, // 還會引發父元素及其下元素的迴流 $("#block2").animate({marginLeft:50});
參考文章:
https://www.cnblogs.com/kevin...
https://blog.csdn.net/allenli...
https://www.css88.com/archive...