你該瞭解的頁面渲染原理與性能優化

首先,你應該瞭解的就是,瀏覽器是如何渲染一個頁面的。javascript

先看一個大體的流程圖css

它的整體流程是這樣的:html

1)瀏覽器解析這三個東西:java

  • 解析HTML/XHTML/SVG,生成DOM樹(事實上,Webkit有三個C++的類對應這三類文檔以用於解析)。
  • 解析css文件產生CSS Rule樹(css規則樹)。
  • 解析javascript,經過DOM API和CSSOM API來操做DOM樹和CSS Rule樹。 

2)解析完成後,瀏覽器會根據DOM樹和CSS Rule樹來構造渲染樹(Rendering Tree)。瀏覽器

  • 渲染樹並不徹底等同於DOM樹,由於一些display:none的東西就不必放在渲染樹中了。
  • CSS Rule樹主要是爲了完成匹配並把CSS Rule附加到渲染樹上的每一個DOM結點。
  • 而後,計算每一個DOM節點的位置,這又叫layout和reflow過程。

3)最後經過調用操做系統Native GUI的API繪製(painting)。緩存

拋去其中的細節,再簡單一點的說法就是:DOM樹解析->css解析->渲染(也就是構建渲染樹以及最終呈現到瀏覽器上的過程)性能優化

這裏 主要針對第三步的渲染過程進行一下講解:app

  1. 計算css樣式,這一步對應着整體流程中的這句話,CSS Rule樹主要是爲了完成匹配並把CSS Rule附加到渲染樹上的每一個DOM結點,也就是說,最終的渲染樹映射了 DOM 的結構。在渲染樹中,每個文本字符串都被當作一個獨立的 renderer。每一個渲染對象都包含了與之對應的計算過樣式的DOM 對象(或者一個文本塊)。換句話說,渲染樹描述了 DOM 的直觀的表現形式。
  2. 構建Render Tree。
  3. Layout – 定位座標和大小,是否換行,各類position, overflow, z-index屬性 ……
  4. 正式開畫

 須要注意的是:Javascript若是動態修改了DOM屬性或是CSS屬會致使從新Layout(Reflow),固然有些屬性改變不會。異步

 這裏,這裏重要要說兩個概念,一個是Reflow,另外一個是Repaint佈局

 Repaint(重繪)

  當在頁面上修改了一些不須要改變定位的樣式的時候(好比background-color,border-color,visibility),瀏覽器只會將新的樣式從新繪製給元素(這就叫一次「重繪」或者「從新定   義樣式」)。這時只須要屏幕的一部分要重畫。

  Reflow(重排)

   當頁面上的改變影響了文檔內容、結構或者元素定位時,就會發生重排(或稱「從新佈局」)。重排一般由如下改變觸發:

  • DOM 操做(如元素增、刪、改或者改變元素順序)
  • 內容的改變,包括 Form 表單中文字的變化
  • 計算或改變 CSS 屬性
  • 增長或刪除一個樣式表
  • 瀏覽器窗口的操做(改變大小、滾動窗口)
  • 激活僞類(如:hover狀態)         

   這時,咱們須要從新驗證並計算Render Tree。是Render Tree的一部分或所有發生了變化。這就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式      佈局,因此,若是某元件的幾何尺寸發生了變化,須要從新佈局,也就叫reflow)reflow 會從<html>這個root frame開始遞歸往下,依次計算全部的結點幾何尺寸和位置,在      reflow過程當中,可能會增長一些frame,好比一個文本字符串必需被包裝起來。

能夠看出,這兩個動做對於瀏覽器的性能都有較大的影響,固然reflow的成本比repaint的成本高好多。那麼,瀏覽器又是如何避免成本增長,從而優化渲染的呢?

瀏覽器如何優化渲染?

    一、瀏覽器盡最大努力限制重排的過程僅覆蓋已更改的元素的區域。舉個例子,一個 position 爲 absolue 或 fixed 的元素的大小變化隻影響它自身和子孫元素,而對一個 position 爲 static 的元素作一樣的操做就會引發全部它後面元素的重排。

    二、當運行一段Jjavascript 代碼的時候,瀏覽器會將一些修改緩存起來,而後當代碼執行的時候,一次性的將這些修改執行。舉例來講,這段代碼會觸發一次重繪和一次重排:

 

var bstyle = document.body.style; // cache
 bstyle.padding = "20px"; // reflow, repaint
 bstyle.border = "10px solid red"; //  再一次的 reflow 和 repaint
 bstyle.color = "blue"; // repaint
bstyle.backgroundColor = "#fad"; // repaint
 bstyle.fontSize = "2em"; // reflow, repaint
 // new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

 瀏覽器不會像上面那樣,你每改一次樣式,它就reflow或repaint一次。通常來講,瀏覽器會把這樣的(都是設置style屬性,而不涉及其餘相似讀取屬性的操做)操做積攢一批,而後作一次reflow,這又叫異步reflow或增量異步reflow。可是有些狀況瀏覽器是不會這麼作的,好比:resize窗口,改變了頁面默認的字體,等。對於這些操做,瀏覽器會立刻進行reflow。

可是有些時候,咱們的腳本會阻止瀏覽器這麼幹,好比:若是咱們請求下面的一些DOM值:(好比咱們在上面的例子中若加一個讀取屬性的操做則會引發又一次的重排)

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. IE中的 getComputedStyle(), 或 currentStyle

由於,若是咱們的程序須要這些值,那麼瀏覽器須要返回最新的值,而這樣同樣會flush出去一些樣式的改變,從而形成頻繁的reflow/repaint。

固然,咱們能夠經過改變書寫習慣而作一些認爲的性能優化:

實際優化建議

  • 建立合法的 HTML 和 CSS ,別忘了制定文件編碼,Style 應該寫在 head 標籤中,script 標籤應該加載 body 標籤結束的位置
  • 試着簡化和優化 CSS 選擇器(這個優化點被大多數使用 CSS 預處理器的開發者忽略了)。將嵌套層數控制在最小。
  • 在你的腳本中,儘量的減小 DOM 的操做。把全部東西都緩存起來,包括屬性和對象(若是它可被重複使用)。進行復雜的操做的時候,最好操做一個「離線」的元素(「離線」元素的意思是與 DOM 對象分開、僅存在內存中的元素),而後將這個元素插入到 DOM 中。

        例如:

           一、使用documentFragment 對象在內存裏操做DOM,相似如下的代碼示例:

// Create the fragment
var fragment = document.createDocumentFragment();
//add DOM to fragment 
for(var i = 0; i < 10; i++) {
    var spanNode = document.createElement("span");
    spanNode.innerHTML = "number:" + i;
    fragment.appendChild(spanNode);
}
//add this DOM to body
document.body.appendChild(spanNode);

        二、先把DOM給display:none(有一次reflow),而後你想怎麼改就怎麼改。好比修改100次,而後再把他顯示出來。

            三、clone一個DOM結點到內存裏,而後想怎麼改就怎麼改,改完後,和在線的那個的交換一下

  • 不要一條一條地修改DOM的樣式。與其這樣,還不如預先定義好css的class,而後修改DOM的className
  • 儘量的只對 position 爲 absolute 或 fix 的元素作動畫
  • 當滾動時禁用一些複雜的 :hover 動畫是一個很好的主意(例如,給 body 標籤加一個 no-hover 的 class
  • 千萬不要使用table佈局。由於可能很小的一個小改動會形成整個table的從新佈局
相關文章
相關標籤/搜索