天天看點

前端4-1-6:浏覽器的回流、重繪、優化

題外話:有時候呢,學着學着就卡機了>-<....又到了記小本本時刻。

前端4-1-6:浏覽器的回流、重繪、優化

目錄

1、浏覽器渲染大緻流程

2、生成渲染樹

3、回流(reflow)

3-1、定義

3-2、會導緻回流的操作

3-3、常用且會導緻回流的屬性和方法

4、重繪(repaint)

4-1、定義

4-2、性能影響

4-3、浏覽器會立刻清空隊列的通路屬性或方法

5、優化

5-1、CSS

5-2、JS

5-3、DocumentFragment

首先,先回顧下浏覽器渲染過程吧!

1、浏覽器渲染大緻流程

  1. 處理 HTML 标記并構造 DOM 樹。
  2. 處理 CSS 标記并構造 CSSOM 樹。
  3. 将 DOM 與 CSSOM 合并成一個渲染樹。
  4. 根據渲染樹來布局,以計算每個節點的幾何資訊。
  5. 将各個幾點繪制到螢幕上。/

2、生成渲染樹

建構渲染樹,浏覽器需要做一下工作:

  1. 從 DOM 樹的根節點開始周遊每個 可見 節點。
  2. 對于每個可見的節點,找到 CSSOM 樹中對應的規則,并應用它們。
  3. 根據每個可見節點以及其對應的樣式,組合生成渲染樹。

第一步周遊節點的時候,需要知道什麼節點是不可見的。

  • 一些不會渲染輸出的節點。比如 script、meta、link等。
  • 某些節點通過 CSS 隐藏。注意 visibility: hidden 與 display: none 是不一樣的。
  • 前者隐藏元素,但元素仍占據着布局空間(即将其渲染成一個空框),而後者 (display: none) 将元素從渲染樹中完全移除,元素既不可見,也不是布局的組成部分。
  • 注意: 渲染樹隻包含可見的節點

3、回流(reflow)

當我們對 DOM 的修改引發了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隐藏元素等)時,浏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會是以受到影響),然後再将計算的結果繪制出來。這個過程就是回流(也叫重排)。

3-1、定義

元素的大小或者位置發生改變(當頁面布局發生改變的時候),觸發了重新布局導緻渲染樹重新計算布局和渲染。

  • ​如添加或删除可見的DOM元素
  • 元素的位置發生變化
  • 元素的尺寸發生變化
  • 内容發生變化(如文本變化或圖檔被另一個不同尺寸的圖檔所代替)
  • 頁面一開始渲染的時候(無法避免)

3-2、會導緻回流的操作

  • 頁面首次渲染。
  • 浏覽器視窗大小發生改變。
  • 元素尺寸或者位置發生改變。
  • 元素内容變化(文字數量或者圖檔大小發生改變)。
  • 元素字型大小的改變。
  • 添加或者删除可見的 

    DOM

     元素。
  • 激活 

    CSS

     僞類 (eg: 

    :hover

    )。
  • 查詢某些屬性或調用某些方法。

3-3、常用且會導緻回流的屬性和方法

  • clientWidth

    clientHeight

    clientTop

    clientLeft

  • offsetWidth

    offsetHeight

    offsetTop

    offsetLeft

  • scrollWidth

    scrollHeight

    scrollTop

    scrollLeft

  • scrollIntoView()

    scrollIntoViewIfNeeded()、scrollTo()

  • getComputedStyle()

    getBoundingClientRect()

4、重繪(repaint)

當我們對 DOM 的修改導緻了樣式的變化、卻并未影響其幾何屬性(比如修改了顔色或背景色)時,浏覽器不需重新計算元素的幾何屬性、直接為該元素繪制新的樣式(跳過了上圖所示的回流環節)。這個過程叫做重繪。

4-1、定義

隻改變自身樣式,不會影響到其他元素。

  • 元素樣式的改變(但寬高、大小、位置不變)
  • eg:   visibility、color、background-color等

注意:回流一定會觸發重繪,而重繪不一定會回流

4-2、性能影響

  • 有時即使僅僅回流一個單一的元素,它的父元素以及任何跟随它的元素也會産生回流
  • 現代浏覽器會對頻繁的回流或重繪操作進行優化:浏覽器會維護一個隊列,把所有引起回流和重繪的操作放入隊列中,如果隊列中的任務數量或者時間間隔達到一個門檻值的,浏覽器就會将隊列清空,進行一次批處理,這樣可以把多次回流和重繪變成一次。

4-3、浏覽器會立刻清空隊列的通路屬性或方法

當你通路以下屬性或方法時,浏覽器會立刻清空隊列:

  • clientWidth

    clientHeight

    clientTop

    clientLeft

  • offsetWidth

    offsetHeight

    offsetTop

    offsetLeft

  • scrollWidth

    scrollHeight

    scrollTop

    scrollLeft

  • scrollIntoView()

    scrollIntoViewIfNeeded()

  • getComputedStyle()

  • getBoundingClientRect()

  • scrollTo()

因為隊列中可能會有影響到這些屬性或方法傳回值的操作,即使你希望擷取的資訊與隊列中操作引發的改變無關,浏覽器也會強行清空隊列,確定你拿到的值是最精确的。

5、優化

  • 減少對 render tree 的操作 【合并多次多DOM和樣式的修改】
  • 減少對一些style資訊的請求,盡量利用好浏覽器的優化政策
  1. 添加css樣式而不是利用js控制樣式(我就是想到這種辦法解決回流問題的)
  2. 盡量将需要改變DOM的操作一次完成
  3. 直接改變className,如果動态改變樣式,則使用cssText(考慮沒有優化的浏覽器)
  4. 不要經常通路會引起浏覽器flush隊列的屬性,如果你确實要通路,利用緩存
  5. 讓元素脫離動畫流,減少回流的Render Tree的規模
  6. 将需要多次重排的元素,position屬性設為absolute或fixed,這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設定為絕對定位;
  7. 盡量不要使用表格布局,如果沒有定寬表格一列的寬度由最寬的一列決定,那麼很可能在最後一行的寬度超出之前的列寬,引起整體回流造成table可能需要多次計算才能确定好其在渲染樹中節點的屬性,通常要花3倍于同等元素的時間。

5-1、CSS

  1. 直接改變className. (把要修改的樣式集中到一個 class 内統一修改);
  2. 避免使用 

    table

     布局 (盡量不要使用表格布局,如果沒有定寬表格一列的寬度由最寬的一列決定,那麼很可能在最後一行的寬度超出之前的列寬,引起整體回流造成table可能需要多次計算才能确定好其在渲染樹中節點的屬性,通常要花3倍于同等元素的時間。)
  3. 盡可能在DOM樹的最末端改變class,盡可能在DOM樹的裡面改變class(可以限制回流的範圍)
  4. 将需要多次重排的元素,position屬性設為absolute或fixed,這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設定為絕對定位;
  5. 使用display:none技術,隻引發兩次回流和重繪;

5-2、JS

  1. 避免頻繁操作樣式,最好一次性重寫style屬性,或者将樣式清單定義為class并一次性更改class屬性。(而不是利用js控制樣式)
  2. 不要經常通路會引起浏覽器緩存隊列的屬性(上述那些浏覽器會立刻清空隊列的屬性)。如果确實要通路,利用緩存。eg:
    // bad
    for (var i = 0; i < len; i++) {
      el.style.left = el.offsetLeft + x + "px";
      el.style.top = el.offsetTop + y + "px";
    }
    // good
    var x = el.offsetLeft,
        y = el.offsetTop;
    for (var i = 0; i < len; i++) {
      x += 10;
      y += 10;
      el.style = x + "px";
      el.style = y + "px";
    }
               
  3. 盡量将需要改變DOM的操作一次完成
    let box = document.getElementById("box").style;
    // bad
    box.color = "red";    // 重繪
    box.size = "14px";    // 回流、重繪
    // good
    box.bord = '1px solid red'
               
  4. 對具有複雜動畫的元素使用絕對定位,使它脫離文檔流,否則會引起父元素及後續元素頻繁回流
  5. 避免頻繁操作 

    DOM

    ,建立一個

    documentFragment

    ,在它上面應用有 

    DOM

     操作,最後再把它添加到文檔中

5-3、DocumentFragment

  • 文檔片段接口,表示一個沒有父級檔案的最小文檔對象
  • 與 

    Document

     最大的差別是因為 

    DocumentFragment

     不是真實 

    DOM

     樹的一部分,它的變化不會觸發 

    DOM

     樹的(重新渲染) ,且不會導緻性能等問題
  • 可以使用 

    document.createDocumentFragment

     方法或者構造函數來建立一個空的 

    DocumentFragment。