重排(reflow)和重繪(repaint)

i3_1.jpg

本文首發於個人我的網站: litgod.net

以前面試的大佬問我關於重排重繪的原理和具體操做,一會兒把我問蒙了。回家便默默地把問題記下來,仔細總結……在閱讀了一些文章後,本身也有了必定的理解,因此分享給你們。但願你們也能耐心把這篇文章看完,認真思考,完全掌握這個知識點!css

頁面生成的過程:

1.HTML 被 HTML 解析器解析成 DOM 樹;
2.CSS 被 CSS 解析器解析成 CSSOM 樹;
3.結合 DOM 樹和 CSSOM 樹,生成一棵渲染樹(Render Tree),這一過程稱爲 Attachment;
4.生成佈局(flow),瀏覽器在屏幕上「畫」出渲染樹中的全部節點;
5.將佈局繪製(paint)在屏幕上,顯示出整個頁面。html

第四步和第五步是最耗時的部分,這兩步合起來,就是咱們一般所說的渲染。前端

i3_2.png

渲染:

在頁面的生命週期中,網頁生成的時候,至少會渲染一次。在用戶訪問的過程當中,還會不斷觸發重排(reflow)和重繪(repaint),無論頁面發生了重繪仍是重排,都會影響性能,最可怕的是重排,會使咱們付出高額的性能代價,因此咱們應儘可能避免。面試

重排比重繪大:

大,在這個語境裏的意思是:誰能影響誰?segmentfault

  • 重繪:某些元素的外觀被改變,例如:元素的填充顏色
  • 重排:從新生成佈局,從新排列元素。

就如上面的概念同樣,單單改變元素的外觀,確定不會引發網頁從新生成佈局,但當瀏覽器完成重排以後,將會從新繪製受到這次重排影響的部分。好比改變元素高度,這個元素乃至周邊dom都須要從新繪製。瀏覽器

也就是說:重繪不必定致使重排,但重排必定會致使重繪緩存

重排(reflow):

概念:

當DOM的變化影響了元素的幾何信息(元素的的位置和尺寸大小),瀏覽器須要從新計算元素的幾何屬性,將其安放在界面中的正確位置,這個過程叫作重排。網絡

重排也叫回流,簡單的說就是從新生成佈局,從新排列元素。dom

下面狀況會發生重排:

  • 頁面初始渲染,這是開銷最大的一次重排
  • 添加/刪除可見的DOM元素
  • 改變元素位置
  • 改變元素尺寸,好比邊距、填充、邊框、寬度和高度等
  • 改變元素內容,好比文字數量,圖片大小等
  • 改變元素字體大小
  • 改變瀏覽器窗口尺寸,好比resize事件發生時
  • 激活CSS僞類(例如::hover
  • 設置 style 屬性的值,由於經過設置style屬性改變結點樣式的話,每一次設置都會觸發一次reflow
  • 查詢某些屬性或調用某些計算方法:offsetWidth、offsetHeight等,除此以外,當咱們調用 getComputedStyle方法,或者IE裏的 currentStyle 時,也會觸發重排,原理是同樣的,都爲求一個「即時性」和「準確性」。
常見引發重排屬性和方法 -- -- --
width height margin padding
display border-width border position
overflow font-size vertical-align min-height
clientWidth clientHeight clientTop clientLeft
offsetWudth offsetHeight offsetTop offsetLeft
scrollWidth scrollHeight scrollTop scrollLeft
scrollIntoView() scrollTo() getComputedStyle()
getBoundingClientRect() scrollIntoViewIfNeeded()

重排影響的範圍:

因爲瀏覽器渲染界面是基於流失佈局模型的,因此觸發重排時會對周圍DOM從新排列,影響的範圍有兩種:ide

  • 全局範圍:從根節點html開始對整個渲染樹進行從新佈局。
  • 局部範圍:對渲染樹的某部分或某一個渲染對象進行從新佈局

全局範圍重排:

<body>
  <div class="hello">
    <h4>hello</h4>
    <p><strong>Name:</strong>BDing</p>
    <h5>male</h5>
    <ol>
      <li>coding</li>
      <li>loving</li>
    </ol>
  </div>
</body>

當p節點上發生reflow時,hello和body也會從新渲染,甚至h5和ol都會收到影響。

局部範圍重排:

用局部佈局來解釋這種現象:把一個dom的寬高之類的幾何信息定死,而後在dom內部觸發重排,就只會從新渲染該dom內部的元素,而不會影響到外界。

重繪(Repaints):

概念:

當一個元素的外觀發生改變,但沒有改變佈局,從新把元素外觀繪製出來的過程,叫作重繪。

常見的引發重繪的屬性:

屬性: -- -- --
color border-style visibility background
text-decoration background-image background-position background-repeat
outline-color outline outline-style border-radius
outline-width box-shadow background-size

重排優化建議:

重排的代價是高昂的,會破壞用戶體驗,而且讓UI展現很是遲緩。經過減小重排的負面影響來提升用戶體驗的最簡單方式就是儘量的減小重排次數,重排範圍。下面是一些行之有效的建議,你們能夠用來參考。

減小重排範圍

咱們應該儘可能以局部佈局的形式組織html結構,儘量小的影響重排的範圍。

  • 儘量在低層級的DOM節點上,而不是像上述全局範圍的示例代碼同樣,若是你要改變p的樣式,class就不要加在div上,經過父元素去影響子元素很差。
  • 不要使用 table 佈局,可能很小的一個小改動會形成整個 table 的從新佈局。那麼在不得已使用table的場合,能夠設置table-layout:auto;或者是table-layout:fixed這樣可讓table一行一行的渲染,這種作法也是爲了限制reflow的影響範圍。

減小重排次數

1.樣式集中改變

不要頻繁的操做樣式,對於一個靜態頁面來講,明智且可維護的作法是更改類名而不是修改樣式,對於動態改變的樣式來講,相較每次微小修改都直接觸及元素,更好的辦法是統一在 cssText 變量中編輯。雖然如今大部分現代瀏覽器都會有 Flush 隊列進行渲染隊列優化,可是有些老版本的瀏覽器好比IE6的效率依然低下。

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top = top + "px";

// 當top和left的值是動態計算而成時...
// better 
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

// better
el.className += " className";

2.分離讀寫操做

DOM 的多個讀操做(或多個寫操做),應該放在一塊兒。不要兩個讀操做之間,加入一個寫操做。

// bad 強制刷新 觸發四次重排+重繪
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
div.style.right = div.offsetRight + 1 + 'px';
div.style.bottom = div.offsetBottom + 1 + 'px';


// good 緩存佈局信息 至關於讀寫分離 觸發一次重排+重繪
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
var curRight = div.offsetRight;
var curBottom = div.offsetBottom;

div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
div.style.right = curRight + 1 + 'px';
div.style.bottom = curBottom + 1 + 'px';

原來的操做會致使四次重排,讀寫分離以後實際上只觸發了一次重排,這都得益於瀏覽器的渲染隊列機制:

當咱們修改了元素的幾何屬性,致使瀏覽器觸發重排或重繪時。它會把該操做放進渲染隊列,等到隊列中的操做到了必定的數量或者到了必定的時間間隔時,瀏覽器就會批量執行這些操做。

3.將 DOM 離線

「離線」意味着不在當前的 DOM 樹中作修改,咱們能夠這樣作:

  • 使用 display:none

    一旦咱們給元素設置 display:none 時(只有一次重排重繪),元素便不會再存在在渲染樹中,至關於將其從頁面上「拿掉」,咱們以後的操做將不會觸發重排和重繪,添加足夠多的變動後,經過 display屬性顯示(另外一次重排重繪)。經過這種方式即便大量變動也只觸發兩次重排。另外,visibility : hidden 的元素只對重繪有影響,不影響重排。

  • 經過 documentFragment 建立一個 dom 碎片,在它上面批量操做 dom,操做完成以後,再添加到文檔中,這樣只會觸發一次重排。
  • 複製節點,在副本上工做,而後替換它!

4.使用 absolute 或 fixed 脫離文檔流

使用絕對定位會使的該元素單獨成爲渲染樹中 body 的一個子元素,重排開銷比較小,不會對其它節點形成太多影響。當你在這些節點上放置這個元素時,一些其它在這個區域內的節點可能須要重繪,可是不須要重排。

5.優化動畫

  • 能夠把動畫效果應用到 position屬性爲 absolutefixed 的元素上,這樣對其餘元素影響較小。

    動畫效果還應犧牲一些平滑,來換取速度,這中間的度本身衡量:
    好比實現一個動畫,以1個像素爲單位移動這樣最平滑,可是Layout就會過於頻繁,大量消耗CPU資源,若是以3個像素爲單位移動則會好不少

  • 啓用GPU加速
    GPU 硬件加速是指應用 GPU 的圖形性能對瀏覽器中的一些圖形操做交給 GPU 來完成,由於 GPU 是專門爲處理圖形而設計,因此它在速度和能耗上更有效率。

    GPU 加速一般包括如下幾個部分:Canvas2D,佈局合成, CSS3轉換(transitions),CSS3 3D變換(transforms),WebGL和視頻(video)。

如何在瀏覽器中查看頁面渲染時間

1.打開開發者工具:點擊 Performance 左側有個小圓點 點擊刷新頁面會錄製整個頁面加載出來 時間的分配狀況。以下圖
i3_3.png

  • 藍色: 網絡通訊和HTML解析
  • 黃色: JavaScript執行
  • 紫色: 樣式計算和佈局,即重排
  • 綠色: 重繪

哪一種色塊比較多,就說明性能耗費在那裏。色塊越長,問題越大。

2.點擊 Event Log:單獨勾選 Loading 項會顯示 html 和 css 加載時間。以下圖:

i3_4.png

3.解析完 DOM+CSSOM 以後會生成一個渲染樹 Render Tree,就是 DOM 和 CSSOM 的一一對應關係。

4.經過渲染樹中在屏幕上「畫」出的全部節點,稱爲渲染。

小結:

  • 渲染的三個階段 Layout,Paint,Composite Layers。
    Layout:重排,又叫回流。
    Paint:重繪,重排重繪這些步驟都是在 CPU 中發生的。
    Compostite Layers:CPU 把生成的 BitMap(位圖)傳輸到 GPU,渲染到屏幕。
  • CSS3 就是在 GPU 發生的:Transform Opacity。在 GPU 發生的屬性比較高效。因此 CSS3 性能比較高。

結語

很是感謝你看完了這篇很長的文章,也但願你們能重視重排的這些問題,在咱們平時的開發中,也須要有意識的規避這些問題,才能讓咱們寫出來的代碼更規範!

參考文檔

掌握瀏覽器重繪(repaint)重排(reflow))-前端進階
csstriggers
CSS硬件加速的好與壞

xiaoshenju.png

相關文章
相關標籤/搜索