帶你從新認識瀏覽器的渲染機制——重繪重排

性能優化中,減小重繪重排應該是一種很好的優化方式,咱們具體看一下什麼狀況下會形成重繪重排,爲何減小重繪重排能夠作到優化,怎麼樣減小重繪重排。css

瀏覽器渲染過程

咱們先看看當瀏覽器拿到服務端返回的資源時,是如何渲染的。html

首先瀏覽器會進行文件解析,主要解析三個東西:jquery

  1. 解析 html/xhtml/svg,造成 dom 樹。
  2. 解析 css,產生 CSS Rule Tree。
  3. 解析 js,js 會經過 api(包括 DOM API 和 CSSOM API) 來操做 Dom Tree 和 CSS Rule Tree。

解析完成以後api

  1. 經過 CSS Rule Tree 和 Dom Tree 生成 rendering Tree。其中不包括 display:none 的元素。
  2. 計算 DOM 節點的位置,對元素進行分層及佈局,也叫 reflow 和 layout 過程。

佈局完成以後,就要進行繪製了,將各層發給 GPU,GPU 將各層合成,顯示在屏幕上,即 composite。瀏覽器

什麼狀況下會形成重繪重排

當咱們開始繪製的時候,若是使用 js 操做了 dom 元素,或者改變了 css 屬性,就可能會形成重繪(repaint)和重排(reflow)。性能優化

repaint:屏幕的一部分進行了重畫,好比某個 css 中改變背景色,元素尺寸沒有變。 reflow:任何一個元素的尺寸發生了變化,須要從新驗證並計算 render tree,就會形成重排。app

在 PC 時代,咱們用 jquery 進行獲取元素,改變元素的尺寸,及時發生重排,咱們也很難感知到,可是當移動時代到來以後,若是頻繁發生重排,那手機就會受不了了。dom

尤爲是在執行下面操做時,成本會很高:svg

  1. js 添加或者刪除元素的時候
  2. js 改變元素的位置發生改變時
  3. 頁面初始化
  4. 容器尺寸發生變化。好比在標準盒模型下添加 padding,border 就會形成重排,因此在書寫樣式的時候,不少會將盒模型設計成怪異盒模型(box-sizing: border-box)
  5. js 獲取元素的尺寸單位。
    • offsetTop, offsetLeft, offsetWidth, offsetHeight
    • scrollTop/Left/Width/Height
    • clientTop/Left/Width/Height
    • IE 中的 getComputedStyle(), 或 currentStyle

若是發生上述的行爲基本都會形成重繪和重排。工具

當發生重排時,必定會發生重繪,可是發生重繪不必定會發生重排。

瀏覽器中每一個元素節點都有 reflow 方法,當一個元素髮生 reflow 時,他的子節點都會發生 reflow。

舉幾個例子來講明一下形成重繪重排的狀況:

var bodyStyle = document.body.style; // cache

bodyStyle.padding = '20px'; // reflow, repaint
bodyStyle.border = '10px solid red'; // 再一次的 reflow 和 repaint

bodyStyle.color = 'blue'; // repaint
bodyStyle.backgroundColor = '#fad'; // repaint

bodyStyle.fontSize = '2em'; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('children!'));
複製代碼

爲何減小重繪重排能夠優化性能

前面說了瀏覽器的渲染機制,多一次重繪就須要瀏覽器從新進行一次繪製,及時 GPU 處理會比較快,可是也是吃不消的,更別說重排了,重排一個 dom,會從新生成 render Tree,而後從新繪製。

如何減小重繪重排

其實瀏覽器很聰明,不可能每次修改樣式就 reflow 或者 repaint 一次,通常來講,瀏覽器會積累一批操做,而後作一次 reflow。

可是也有些例外狀況,好比 resize 窗口,改變窗口字體,瀏覽器會當即進行 reflow。

雖然瀏覽器會這麼作,可是咱們也應該減小重繪重排的次數,在開發階段就爲瀏覽器進行特殊的關愛,畢竟是天天陪伴咱們的小夥伴。

下面總結了一些針對 reflow 和 repaint 的最佳實踐:

  1. 不要一條一條的修改 DOM 樣式,儘可能提早設置好 class,後續增長 class,進行批量修改。
  2. 把 DOM 離線後修改。
    • 使用 documentFragment 對象在內存裏操做 DOM。
    • 使用 requestAnimationFrame 能夠進行優化,在下一幀進行操做。
    • 把修改頻繁的元素先 display: none,修改完以後顯示,修改個 100 次也無妨。
    • clone 一個 dom 節點在內存裏,修改以後;與在線的節點相替換。
  3. 不要把 DOM 元素的屬性值放到一個循環中當成循環的變量。
  4. 儘量的修改低層級的節點,避免修改高層級的節點,形成大面積的 reflow。
  5. 千萬不要使用 table 佈局,修改一小塊地方,會形成整個 table 從新佈局。
  6. 動畫儘可能使用 css 動畫,css 動畫中儘可能只使用 transform 和 opacity,這不會發生重排和重繪

composite

當每次佈局完成以後,就會發生 composite 過程,瀏覽器都把重繪後的圖像發給 GPU 去合成並顯示。

在上面最佳實踐中最後提到了動畫,動畫實際上是比較耗費性能的,由於動畫的每一幀都會發給 GPU 去合成,重繪重排會發生在動畫的每一幀。

咱們在寫動畫的時候,能夠經過 js 寫,也能夠經過 css 寫。兩種方式在寫動畫時,過程也是不同的。

  1. js 寫的動畫,過程:js 計算 -> 重排(若佈局改變) -> 重繪 -> 合成
  2. css 動畫,過程:重排(若佈局改變)-> 重繪 -> 合成

因此不難看出,耗費性能最少並能並最流暢的動畫是隻觸發合成。

爲了僅發生 composite,咱們作動畫的 css property 必須知足如下三個條件:

  1. 不影響文檔流。
  2. 不依賴文檔流。
  3. 不會形成重繪。

知足以上以上條件的 css property 只有 transform 和 opacity。

這樣的話,因爲沒有重排和重繪,只有合成,那麼瀏覽器在動畫執行以前就知道動畫如何開始和結束。

而且有兩個優點:

  1. 動畫將會很是流暢
  2. 動畫不在綁定到 CPU,即便 js 執行大量的工做;動畫依然流暢

事實上影響動畫流暢性的因素不止重排重繪,還有 CPU 內存。

css 動畫有一個重要的特性,它是徹底工做在 GPU 上。由於你聲明瞭一個動畫如何開始和如何結束,瀏覽器會在動畫開始前準備好全部須要的指令;並把它們發送給 GPU。

而若是使用 js 動畫,瀏覽器必須計算每一幀的狀態;爲了保證平滑的動畫,咱們必須在瀏覽器主線程計算新狀態;把它們發送給 GPU 至少 60 次每秒。

除了計算和發送數據比 css 動畫要慢,主線程的負載也會影響動畫; 當主線程的計算任務過多時,會形成動畫的延遲、卡頓。

因此最佳實踐中最後一條就提到了,在寫動畫時,儘可能寫 css 動畫,而且儘可能用 transform 和 opacity。

輔助工具

谷歌瀏覽器檢測重繪工具:右上角...,more tools,rendering。

勾選了 paint flashing 時,在刷新頁面以後,就會發現有有綠色的背景,有綠色的背景就表明着重排。

還能夠勾選 FPS meter,能夠查看渲染頁面時候 GPU 的使用率。

方便發現本身在開發過程當中,哪些操做形成了重繪重排,儘可能減小修改次數,優化性能。

相關文章
相關標籤/搜索