前端基本功(四):性能優化之你真的懂迴流、重繪與合成層嗎?

1. 頁面的呈現流程

  1. 瀏覽器把獲取到的HTML代碼解析成1個DOM樹,HTML中的每一個tag都是DOM樹中的1個節點,根節點就是咱們經常使用的document對象。DOM樹裏包含了全部HTML標籤,包括display:none隱藏,還有用JS動態添加的元素等。
  2. 瀏覽器把全部樣式(用戶定義的CSS和用戶代理)解析成樣式結構體,在解析的過程當中會去掉瀏覽器不能識別的樣式,好比IE會去掉-moz開頭的樣式,而FF會去掉_開頭的樣式。
  3. DOM Tree 和樣式結構體組合後構建render tree, render tree相似於DOM tree,但區別很大,render tree能識別樣式,render tree中每一個NODE都有本身的style,並且 render tree不包含隱藏的節點 (好比display:none的節點,還有head節點),由於這些節點不會用於呈現,並且不會影響呈現的,因此就不會包含到 render tree中。注意 visibility:hidden隱藏的元素仍是會包含到 render tree中的,由於visibility:hidden 會影響佈局(layout),會佔有空間。根據CSS2的標準,render tree中的每一個節點都稱爲Box (Box dimensions),理解頁面元素爲一個具備填充、邊距、邊框和位置的盒子。
  4. 一旦render tree構建完畢後,瀏覽器就能夠根據render tree來繪製頁面了。

image

2. 什麼是迴流與重繪

  1. 當render tree中的一部分(或所有)由於元素的規模尺寸,佈局,隱藏等改變而須要從新構建。這就稱爲迴流(reflow)。每一個頁面至少須要一次迴流,就是在頁面第一次加載的時候。在迴流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並從新構造這部分渲染樹,完成迴流後,瀏覽器會從新繪製受影響的部分到屏幕中,該過程成爲重繪。
  2. 當render tree中的一些元素須要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響佈局的,好比background-color。則就叫稱爲重繪。
  3. 迴流必將引發重繪,而重繪不必定會引發迴流。

repaint,就是瀏覽器得知元素產生了不影響排版的狀況下後對這個元素進行從新繪製的過程。例如咱們改變了元素的顏色,加個下劃線等。前端

reflow, 瀏覽器得知元素產生了對文檔樹排版有影響的樣式變化,對全部受影響的dom節點進行從新排版工做node

3. 迴流發生場景

當頁面佈局和幾何屬性改變時就須要迴流。git

  1. 添加或者刪除可見的DOM元素;
  2. 元素位置改變;
  3. 元素尺寸改變——邊距、填充、邊框、寬度和高度
  4. 內容改變——好比文本改變或者圖片大小改變而引發的計算值寬度和高度改變;
  5. 頁面渲染初始化;
  6. 瀏覽器窗口尺寸改變——resize事件發生時;
var s = document.body.style;
s.padding = "2px"; // 迴流+重繪
s.border = "1px solid red"; // 再一次 迴流+重繪
s.color = "blue"; // 再一次重繪
s.backgroundColor = "#ccc"; // 再一次 重繪
s.fontSize = "14px"; // 再一次 迴流+重繪
// 添加node,再一次 迴流+重繪
document.body.appendChild(document.createTextNode('abc!'));
複製代碼

4.迴流與重繪的影響

迴流比重繪的代價要更高,迴流的花銷跟render tree有多少節點須要從新構建有關係,假設你直接操做body,好比在body最前面插入1個元素,會致使整個render tree迴流,這樣代價固然會比較高,但若是是指body後面插入1個元素,則不會影響前面元素的迴流。github

5. 瀏覽器如何處理

每句JS操做都去迴流重繪的話,瀏覽器可能就會受不了。瀏覽器

瀏覽器會維護1個隊列,把全部會引發迴流、重繪的操做放入這個隊列,等隊列中的操做到了必定的數量或者到了必定的時間間隔,瀏覽器就會flush隊列,進行一個批處理。這樣就會讓屢次的迴流、重繪變成一次迴流重繪。緩存

雖然有了瀏覽器的優化,但有時候咱們寫的一些代碼可能會強制瀏覽器提早flush隊列,這樣瀏覽器的優化可能就起不到做用了。當你請求向瀏覽器請求一些 style信息的時候,就會讓瀏覽器flush隊列:性能優化

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. width,height
  5. 請求了getComputedStyle(), 或者 IE的 currentStyle

當你請求上面的一些屬性的時候,瀏覽器爲了給你最精確的值,須要flush隊列,由於隊列中可能會有影響到這些值的操做。即便你獲取元素的佈局和樣式信息跟最近發生或改變的佈局信息無關,瀏覽器都會強行刷新渲染隊列。引擎會從新渲染來確保獲取的值 是實時的。bash

6. 如何減小回流與重繪

減小回流、重繪其實就是須要減小對render tree的操做(合併屢次多DOM和樣式的修改),並減小對一些style信息的請求,儘可能利用好瀏覽器的優化策略。app

  1. 對Render Tree的計算一般只須要遍歷一次就能夠完成,但table及其內部元素除外,他們可能須要屢次計算,一般要花3倍於同等元素的時間,這也是爲何要避免使用table佈局的緣由之一。
  2. 儘量在DOM樹的最末端改變class。避免設置多層內聯樣式。將動畫效果應用到position屬性爲absolute或fixed的元素上。避免使用CSS表達式(例如:calc())。
  3. 避免頻繁操做樣式,最好一次性重寫style屬性,或者將樣式列表定義爲class並一次性更改class屬性。避免頻繁操做DOM,建立一個documentFragment,在它上面應用全部DOM操做,最後再把它添加到文檔中。也能夠先爲元素設置display: none,操做結束後再把它顯示出來。由於在display屬性爲none的元素上進行的DOM操做不會引起迴流和重繪。避免頻繁讀取會引起迴流/重繪的屬性,若是確實須要屢次使用,就用一個變量緩存起來。對具備複雜動畫的元素使用絕對定位,使它脫離文檔流,不然會引發父元素及後續元素頻繁迴流。

6. 再次理解display:none 與 visibility:hidden 的異同

  1. 二者均可以在頁面上隱藏節點。
    • display:none 隱藏後的元素不佔據任何空間。它的寬度、高度等各類屬性值都將「丟失」
    • visibility:hidden 隱藏的元素空間依舊存在。它仍具備高度、寬度等屬性值
  2. 性能的角度而言,便是迴流與重繪的方面。
    • display:none 會觸發 reflow(迴流)
    • visibility:hidden 只會觸發 repaint(重繪),由於沒有發現位置變化

他們二者在優化中 visibility:hidden 會顯得更好,由於咱們不會由於它而去改變了文檔中已經定義好的顯示層次結構了。dom

  1. 對子元素的影響
    • display:none 一旦父節點元素應用了 display:none,父節點及其子孫節點元素所有不可見,並且不管其子孫元素如何設置 display 值都沒法顯示;
    • visibility:hidden 一旦父節點元素應用了 visibility:hidden,則其子孫後代也都會所有不可見。不過存在隱藏「失效」的狀況。當其子孫元素應用了 visibility:visible,那麼這個子孫元素又會顯現出來。

無線性能優化:Composite

一個 Web 頁面的展現,簡單來講能夠認爲經歷瞭如下下幾個步驟。

image

性能優化

提高爲合成層簡單說來有如下幾點好處:

  1. 合成層的位圖,會交由 GPU 合成,比 CPU 處理要快
  2. 當須要 repaint 時,只須要 repaint 自己,不會影響到其餘的層
  3. 對於 transform 和 opacity 效果,不會觸發 layout 和 paint

1. 提高動畫效果的元素

合成層的好處是不會影響到其餘元素的繪製,所以,爲了減小動畫元素對其餘元素的影響,從而減小 paint,咱們須要把動畫效果中的元素提高爲合成層。

提高合成層的最好方式是使用 CSS 的 will-change 屬性。從上一節合成層產生緣由中,能夠知道 will-change 設置爲 opacity、transform、top、left、bottom、right 能夠將元素提高爲合成層。

#target {
  will-change: transform; //兼容性很差
}
//對於那些目前還不支持 will-change 屬性的瀏覽器
//目前經常使用的是使用一個 3D transform 屬性來強制提高爲合成層
#target {
  transform: translateZ(0);
}
複製代碼

但須要注意的是,不要建立太多的渲染層。由於每建立一個新的渲染層,就意味着新的內存分配和更復雜的層的管理。

若是你已經把一個元素放到一個新的合成層裏,那麼可使用 Timeline 來確認這麼作是否真的改進了渲染性能。別盲目提高合成層,必定要分析其實際性能表現。

2. 使用 transform 或者 opacity 來實現動畫效果

其實從性能方面考慮,最理想的渲染流水線是沒有佈局和繪製環節的,只須要作合成層的合併便可:

image

爲了實現上述效果,就須要只使用那些僅觸發 Composite 的屬性。目前,只有兩個屬性是知足這個條件的:transforms 和 opacity。

3. 減小繪製區域

  1. 對於不須要從新繪製的區域應儘可能避免繪製,以減小繪製區域,好比一個 fix 在頁面頂部的固定不變的導航 header,在頁面內容某個區域 repaint 時,整個屏幕包括 fix 的 header 也會被重繪。
  2. 而對於固定不變的區域,咱們指望其並不會被重繪,所以能夠經過以前的方法,將其提高爲獨立的合成層。

4. 合理管理合成層:建立一個新的合成層並非免費的,它得消耗額外的內存和管理資源。實際上,在內存資源有限的設備上,合成層帶來的性能改善,可能遠遠趕不上過多合成層開銷給頁面性能帶來的負面影響。

大多數人都很喜歡使用 translateZ(0) 來進行所謂的硬件加速,以提高性能,可是性能優化並無所謂的「銀彈」,translateZ(0) 不是,本文列出的優化建議也不是。拋開了對頁面的具體分析,任何的性能優化都是站不住腳的,盲目的使用一些優化措施,結果可能會拔苗助長。所以切實的去分析頁面的實際性能表現,不斷的改進測試,纔是正確的優化途徑。

參考文檔:淘寶FED
你們一塊兒學前端地址: front-end-Web-developer-interview
相關文章
相關標籤/搜索