reflow和repaint在pc端只要不是懷有明知山有虎,偏向虎山行的心態寫代碼,這兩貨幾乎不會引起性能問題, 可是移動端的渲染能力和pc端差了不止一個大截,一個不當心reflow和repaint就成了移動端的「性能殺手」。因此瞭解reflow和repaint也是頗有必要的,在考量頁面性能的時候分析reflow和repaint也算是一個切入點。javascript
reflow
迴流,或者叫重排均可以。迴流(reflow)這個名詞指的是瀏覽器爲了從新渲染部分或所有的文檔而從新計算文檔中元素的位置和幾何結構的過程。css
簡單來講就是當頁面佈局或者幾何屬性改變時就須要reflow。html
在一個頁面中至少在頁面剛加載的時候有一次reflow,在reflow的過程當中瀏覽器會將render tree中受影響的節點失效,再從新構建render tree,有時候,即便僅僅迴流一個單一的元素,也可能要求它的父元素以及任何跟隨它的元素也產生迴流java
repaint
重繪,當頁面中的元素只須要更新樣式風格不影響佈局,好比更換背景色background-color,這個過程就是重繪。react
從reflow的定義中就能夠聽出一些來,元素的佈局和幾何屬性改變時就會觸發reflow。主要有這些屬性:程序員
盒模型相關的屬性: width,height,margin,display,border,etcweb
定位屬性及浮動相關的屬性: top,position,float,etcchrome
改變節點內部文字結構也會觸發迴流: text-align, overflow, font-size, line-height, vertival-align,etccanvas
除開這三大類的屬性變更會觸發reflow,如下狀況也會觸發:瀏覽器
頁面中的元素更新樣式風格相關的屬性時就會觸發重繪,如background,color,cursor,visibility,etc
注意:由頁面的渲染過程可知,reflow必將會引發repaint,而repaint不必定會引發reflow
瞭解有哪些屬性值改變會觸發迴流或者重繪點擊這裏
設想一個這樣的場景,咱們須要在一個循環中不斷修改一個dom節點的位置或者是內容
document.addEventListener('DOMContentLoaded', function () {
var date = new Date();
for (var i = 0; i < 70000; i++) {
var tmpNode = document.createElement("div");
tmpNode.innerHTML = "test" + i;
document.body.appendChild(tmpNode);
}
console.log(new Date() - date);
});
複製代碼
這裏屢次測量消耗時間大概在500ms(運行環境均爲pc端,小霸王筆記本)。看到這個結果可能就有疑問了,這裏有70000次內容的修改,就有70000reflow操做,也就用了500ms的時間(歸功於遲緩的dom操做),說好的reflow消耗性能呢。
犯二代碼以下:
document.addEventListener('DOMContentLoaded', function () {
var date = new Date();
for (var i = 0; i < 70000; i++) {
var tmpNode = document.createElement("div");
tmpNode.innerHTML = "test" + i;
document.body.offsetHeight; // 獲取body的真實值
document.body.appendChild(tmpNode);
}
console.log("speed time", new Date() - date);
});
複製代碼
通常人應該不會去運行這種代碼,若是你運行了的話,恭喜你的電腦-1s。可是若是沒有衡量指標,優化性能也就無從談起。
「If you cannot measure it, you cannot improve it.」 -Lord Kelvin
爲了防止瀏覽器假死,把循環次數都改成7000次,得出的結果是(屢次平均):
經過這兩個樣例印證了瀏覽器確實有優化reflow的小動做,聰明的程序員不會依賴瀏覽器的優化策略,在平常開發中遇到for循環就應該慎重編寫循環體內部的代碼。
如何減小reflow和repaint呢?回到定義去,reflow在頁面佈局或者定位發生變化時纔會發生
,從定義中咱們至少能夠得出兩個優化思路
其本質上爲減小對render tree的操做。render tree也就是渲染樹,它的每一個節點都是可見,且包含該節點的內容和對應的規則樣式,這也是render tree和dom數最大的區別所在, 減小reflow操做,主旨是合併多個reflow,最後再反饋到render tree中,諸如:
// 很差的寫法
var left = 1;
var top = 2;
ele.style.left = left + "px";
ele.style.top = top + "px";
// 比較好的寫法
ele.className += " className1";
複製代碼
或者直接修改cssText:
ele.style.cssText += ";
left: " + left + "px;
top: " + top + "px;";
複製代碼
Dom規定文檔片斷(document fragment)是一種「輕量級」的文檔,能夠包含和控制節點,但不會想完整的文檔那樣佔用額外的資源。雖然不能把文檔片斷直接添加到文檔中,可是能夠將它做爲一個「倉庫」來使用,便可以在裏面保存未來可能會添加到文檔中的節點。 好比最開始的樣例結合DocumentFragment就能夠這樣寫:
document.addEventListener('DOMContentLoaded', function () {
var date = new Date(),
fragment = document.createDocumentFragment();
for (var i = 0; i < 7000; i++) {
var tmpNode = document.createElement("div");
tmpNode.innerHTML = "test" + i;
fragment.appendChild(tmpNode);
}
document.body.appendChild(fragment);
console.log("speed time", new Date() - date);
});
複製代碼
將多個修改結果收納到了documentFragment這個「倉庫」中,這個過程並不會影響到render tree,待循環完畢再將這個「倉庫」的「存貨」添加到dom上,以此達到減小reflow的目的,使用cloneNode也是同理。 而使用display:none來下降reflow的性能開銷的原理在於使節點從render tree中失效,等通過多個會觸發reflow操做後再「上線」
// 很差的寫法
for(let i = 0; i < 20; i++ ) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
// 比較好的寫法
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style;
for (let i = 0; i < 20; i++ ) {
left += 5;
top += 5;
s.left = left + "px";
s.top = top + "px";
}
複製代碼
咱們能夠將一些會觸發迴流的屬性替換,來避免reflow。好比用translate代替top,用opacity替代visibility
樣例代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style> #react { position: relative; top: 0; width: 100px; height: 100px; background-color: red; } </style>
</head>
<body>
<div id="react"></div>
<script type="text/javascript"> setTimeout(() => { document.getElementById("react").style.top = "100px" }, 2000); </script>
</body>
</html>
複製代碼
代碼很簡單,頁面上有一個紅色的方塊,2秒後它的top值將會變爲「100px」,爲了方便體現替代的屬性能夠避免reflow這裏咱們使用chrome的開發者工具,部分截圖以下
咱們把這五個過程用時記下:80 + Layout(73) + 72 + 20 + 69 = 316us
再用translate替代top:
- position: relative;
- top: 0;
+ transform: translateY(0);
- document.getElementById("react").style.top = "100px"
+ document.getElementById("react").style.transform = "translateY(100px)"
複製代碼
Performace截圖:
咱們再用opacity去替代visibility試試看。
- document.getElementById("react").style.transform = "translateY(100px)"
+ document.getElementById("react").style.visibility = "hidden"
複製代碼
Performace截圖:
+ opacity: 1;
- document.getElementById("react").style.visibility = "hidden"
+ document.getElementById("react").style.opacity = "0"
複製代碼
按照上面的樣例,應該得出用opacity替代visibility後重繪也就是Paint這個過程會消失從而達到性能提高的目的,既然這樣咱們來看Performace截圖:
其實opacity變化並不能改變這個圖層的內容,改變的只是當前圖層的alpha通道的值,從而來決定這個圖層顯不顯示。可是這opacity變化的元素並非單獨的圖層,而是在document這個圖層上的,以下Layers截圖:
就是說瀏覽器並不能由於圖層裏面有一個opacity爲0的元素就讓整個圖層的alpha通道變爲零,而讓整個圖層不顯示,因此就有了Layout和Paint這兩個過程。解決辦法也很簡單那就是直接讓這個元素單獨爲一個圖層
修改css新建圖層有兩種辦法:
這裏咱們用下面一個
+ transform: translateZ(0);
複製代碼
Performace截圖:
這裏因爲我變更的元素很是簡單,只有一個簡單的div,減小Paint過程帶來的優化收益並非很明顯,若是是Paint過程是毫秒級別減小Paint過程的效果仍是可觀的。
由上述兩個替代會觸發reflow和repaint的屬性取得性能優化收益的例子中能夠看出,這個方法是可行的,除開第一點減小reflow操做和第二點替換屬性之外還有一些方法能夠減小reflow和repaint
減小table的使用
動畫實現的速度選擇
對於動畫新建圖層
table自帶的樣式和一些很是方便的特性會方便咱們的開發,可是table也有一些與生俱來的性能缺陷,若是想要修改表格裏無論哪個單元格,都會致使整張表格的從新Layout,若是這個表格很大,性能的消耗會有一個上升成本的。
在上一個樣例中咱們新建了一個圖層實現了opacity替代visibility去減小repaint的可行性,那麼圖層還有什麼其餘運用嗎?答案是有的,咱們能夠將一些頻繁重繪迴流的DOM元素做爲一個圖層,那麼這個DOM元素的重繪和迴流的影響只會在這個圖層中,固然若是你爲每個元素都建立一個圖層那樣確定也會聰明反被聰明誤,還記得上述的Performance截圖中的過程嗎,最後一個Composite Layers這個過程就是合併多個圖層的,圖層過多這個過程會很是耗時,其實這個過程自己也很是耗時,原則上是在必要的狀況下才會新建圖層來減小重繪和迴流的影響範圍,到底使不使用就須要開發人員在業務情景中balance. 在Chrome瀏覽器下能夠這樣建立圖層:
大致思路就是咱們把頻繁重繪迴流的DOM元素做爲一個圖層,那麼這個DOM元素的重繪和迴流的影響只會在這個圖層中,來提高性能。舉個栗子,咱們打開chrome開發者工具中的Layers,而後打開某網站
簡單回顧一下本文,咱們最開始聊了一下reflow和repaint是什麼,如何觸發它們,接下來談了一下瀏覽器在處理它們所採起的策略,最後就是如何避免reflow和repaint帶來的性能開銷,還補充了一下圖層的存在乎義和簡單運用。 其實在優化reflow和repaint上就是兩點:
https://csstriggers.com
http://blog.csdn.net/luoshengyang/article/details/50661553