很多人都知道要減少瀏覽器的重排和重繪,但對其中的具體原理以及如何具體操作並不是很瞭解,當突然提起這個話題的時候,還是會一臉懵逼。希望大家可以耐著性子閱讀本文,仔細琢磨,徹底掌握這個知識點!
網頁生成過程:
- HTML被HTML解析器解析成DOM 樹
- css則被css解析器解析成CSSOM 樹
- 結合DOM樹和CSSOM樹,生成一棵渲染樹(Render Tree)
- 生成佈局(flow),即將所有渲染樹的所有節點進行平面合成
- 將佈局繪製(paint)在螢幕上
第四步和第五步是最耗時的部分,這兩步合起來,就是我們通常所說的渲染。
網上找了一張圖片,我加了註釋會更直觀一些:
渲染:
網頁生成的時候,至少會渲染一次。
在使用者訪問的過程中,還會不斷重新渲染
重新渲染需要重複之前的第四步(重新生成佈局)+第五步(重新繪製)或者只有第五個步(重新繪製)。
重排比重繪大:
大,在這個語境裡的意思是:誰能影響誰?
- 重繪:某些元素的外觀被改變,例如:元素的填充顏色
- 重排:重新生成佈局,重新排列元素。
就如上面的概念一樣,單單改變元素的外觀,肯定不會引起網頁重新生成佈局,但當瀏覽器完成重排之後,將會重新繪製受到此次重排影響的部分。
比如改變元素高度,這個元素乃至周邊dom都需要重新繪製。
複製程式碼
也就是說:"重繪"不一定會出現"重排","重排"必然會出現"重繪"
重排(reflow):
概念:
當DOM的變化影響了元素的幾何資訊(DOM物件的位置和尺寸大小),瀏覽器需要重新計算元素的幾何屬性,將其安放在介面中的正確位置,這個過程叫做重排。
重排也叫回流,重排的過程以下面這種理解方式更清晰一些:
迴流就好比向河裡(文件流)扔了一塊石頭(dom變化),激起漣漪,然後引起周邊水流受到波及,所以叫做迴流
常見引起重排屬性和方法
任何會改變元素幾何資訊(元素的位置和尺寸大小)的操作,都會觸發重排,下面列一些栗子:
- 新增或者刪除可見的DOM元素;
- 元素尺寸改變——邊距、填充、邊框、寬度和高度
- 內容變化,比如使用者在input框中輸入文字
- 瀏覽器視窗尺寸改變——resize事件發生時
- 計算 offsetWidth 和 offsetHeight 屬性
- 設定 style 屬性的值
常見引起重排屬性和方法 | |||
---|---|---|---|
width | height | margin | padding |
display | border | position | overflow |
clientWidth | clientHeight | clientTop | clientLeft |
offsetWidth | offsetHeight | offsetTop | offsetLeft |
scrollWidth | scrollHeight | scrollTop | scrollLeft |
scrollIntoView() | scrollTo() | getComputedStyle() | |
getBoundingClientRect() | scrollIntoViewIfNeeded() |
重排影響的範圍:
由於瀏覽器渲染介面是基於流失佈局模型的,所以觸發重排時會對周圍DOM重新排列,影響的範圍有兩種:
- 全域性範圍:從根節點
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內部的元素,而不會影響到外界。
儘可能的減少重排的次數、重排範圍:
重排需要更新渲染樹,效能花銷非常大:
它們的代價是高昂的,會破壞使用者體驗,並且讓UI展示非常遲緩,我們需要儘可能的減少觸發重排的次數。
重排的效能花銷跟渲染樹有多少節點需要重新構建有關係:
所以我們應該儘量以區域性佈局的形式組織html
結構,儘可能小的影響重排的範圍。
而不是像全域性範圍的示例程式碼一樣一溜的堆砌標籤,隨便一個元素觸發重排都會導致全域性範圍的重排。
重繪(repaint):
概念:
當一個元素的外觀發生改變,但沒有改變佈局,重新把元素外觀繪製出來的過程,叫做重繪。
常見的引起重繪的屬性:
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 |
瀏覽器的渲染佇列:
思考以下程式碼將會觸發幾次渲染?
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
複製程式碼
根據我們上文的定義,這段程式碼理論上會觸發4次重排+重繪,因為每一次都改變了元素的幾何屬性,實際上最後只觸發了一次重排,這都得益於瀏覽器的渲染佇列機制:
當我們修改了元素的幾何屬性,導致瀏覽器觸發重排或重繪時。它會把該操作放進渲染佇列,等到佇列中的操作到了一定的數量或者到了一定的時間間隔時,瀏覽器就會批量執行這些操作。
強制重新整理佇列:
div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);
複製程式碼
這段程式碼會觸發4次重排+重繪,因為在console
中你請求的這幾個樣式資訊,無論何時瀏覽器都會立即執行渲染佇列的任務,即使該值與你操作中修改的值沒關聯。
因為佇列中,可能會有影響到這些值的操作,為了給我們最精確的值,瀏覽器會立即重排+重繪。
強制重新整理佇列的style樣式請求:
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop, scrollLeft, scrollWidth, scrollHeight
- clientTop, clientLeft, clientWidth, clientHeight
- getComputedStyle(), 或者 IE的 currentStyle
我們在開發中,應該謹慎的使用這些style請求,注意上下文關係,避免一行程式碼一個重排,這對效能是個巨大的消耗
重排優化建議
就像上文提到的我們要儘可能的減少重排次數、重排範圍,這樣說很泛,下面是一些行之有效的建議,大家可以參考一下。
1. 分離讀寫操作
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);
複製程式碼
還是上面觸發4次重排+重繪的程式碼,這次只觸發了一次重排:
在第一個console
的時候,瀏覽器把之前上面四個寫操作的渲染佇列都給清空了。剩下的console,因為渲染佇列本來就是空的,所以並沒有觸發重排,僅僅拿值而已。
2. 樣式集中改變
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
複製程式碼
雖然現在大部分瀏覽器有渲染佇列優化,不排除有些瀏覽器以及老版本的瀏覽器效率仍然低下:
建議通過改變class或者csstext屬性集中改變樣式
// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top = top + "px";
// good
el.className += " theclassname";
// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
複製程式碼
3. 快取佈局資訊
// bad 強制重新整理 觸發兩次重排
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
// good 快取佈局資訊 相當於讀寫分離
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
複製程式碼
4. 離線改變dom
-
隱藏要操作的dom
在要操作dom之前,通過display隱藏dom,當操作完成之後,才將元素的display屬性為可見,因為不可見的元素不會觸發重排和重繪。
dom.display = 'none' // 修改dom樣式 dom.display = 'block' 複製程式碼
-
通過使用DocumentFragment建立一個
dom
碎片,在它上面批量操作dom,操作完成之後,再新增到文件中,這樣只會觸發一次重排。 -
複製節點,在副本上工作,然後替換它!
5. position屬性為absolute或fixed
position屬性為absolute或fixed的元素,重排開銷比較小,不用考慮它對其他元素的影響
6. 優化動畫
-
可以把動畫效果應用到position屬性為absolute或fixed的元素上,這樣對其他元素影響較小
動畫效果還應犧牲一些平滑,來換取速度,這中間的度自己衡量:
比如實現一個動畫,以1個畫素為單位移動這樣最平滑,但是reflow就會過於頻繁,大量消耗CPU資源,如果以3個畫素為單位移動則會好很多。
-
啟用GPPU加速
此部分來自優化CSS重排重繪與瀏覽器效能
GPU(影像加速器):
GPU 硬體加速是指應用 GPU 的圖形效能對瀏覽器中的一些圖形操作交給 GPU 來完成,因為 GPU 是專門為處理圖形而設計,所以它在速度和能耗上更有效率。
GPU 加速通常包括以下幾個部分:Canvas2D,佈局合成, CSS3轉換(transitions),CSS3 3D變換(transforms),WebGL和視訊(video)。
/* * 根據上面的結論 * 將 2d transform 換成 3d * 就可以強制開啟 GPU 加速 * 提高動畫效能 */ div { transform: translate3d(10px, 10px, 0); } 複製程式碼
結語
重排也是導致DOM指令碼執行效率低的關鍵因素之一,重排與重繪作為大廠經常出現的面試題,並且涉及的效能優化,這是前端必須掌握的基本概念/技能之一(敲黑板!)。
重排會不斷觸發這是不可避免的,但我們在開發時,應儘量按照文中的建議來組織程式碼,這種優化,需要平時有意識的去做,一點一滴的去做,希望大家重視一下。
希望看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。
以上2018.12.17
參考資料: