重排與重繪
前言
此處需要首先說(shuō)明一下網(wǎng)頁(yè)從HTML文件變成屏幕上的畫(huà)面所經(jīng)歷的過(guò)程:
- HTML內(nèi)容被HTML解析器解析生成DOM樹(shù)
- CSS內(nèi)容被CSS解析器解析生產(chǎn)CSSOM樹(shù)
- DOM樹(shù)+CSSOM樹(shù)會(huì)生產(chǎn)Render Tree(渲染樹(shù))
- 生成布局艾帐,瀏覽器根據(jù)渲染樹(shù)來(lái)布局贸呢,以計(jì)算每個(gè)節(jié)點(diǎn)的幾何信息
- 將各個(gè)節(jié)點(diǎn)繪制到屏幕上
其中第4步為布局排列(flow),第5步為繪制(paint)弃衍,這兩部加起來(lái)也就是我們通常所說(shuō)的渲染
本次主題所說(shuō)的重排與重繪便是指網(wǎng)頁(yè)重新布局排列和重新繪制敬锐,英文概念分別為:Reflow , Repaint
袜啃,也有些文章中稱(chēng)之為回流與重繪
目的
重繪與重排與我們的前端性能有什么關(guān)系呢盏袄?
在Web前端頁(yè)面的生命周期中芳绩,在網(wǎng)頁(yè)生成時(shí)蛾绎,瀏覽器會(huì)至少渲染一次頁(yè)面昆箕,并且,在用戶訪問(wèn)頁(yè)面過(guò)程中租冠,還可能會(huì)不斷觸發(fā)重排和重繪鹏倘,不管頁(yè)面發(fā)生了重繪還是重排,都會(huì)影響到網(wǎng)頁(yè)的性能顽爹,尤其是其中的重排纤泵,會(huì)使我們付出高額的性能代價(jià),因此我們應(yīng)盡量避免镜粤。
那么哪些操作會(huì)引發(fā)重排與重繪呢捏题?
重繪:元素的外觀被改變,例如:元素的背景顏色發(fā)生變化
重排:重新生成布局肉渴,重新排列元素公荧,例如:元素的尺寸、位置發(fā)生變化
重排(Reflow):
當(dāng)DOM的變化影響了元素的幾何信息(元素的的位置和尺寸大小)同规,瀏覽器需要重新計(jì)算元素的幾何屬性循狰,將其安放在界面中的正確位置窟社,這個(gè)過(guò)程叫做重排。
重排也叫回流绪钥,簡(jiǎn)單的說(shuō)就是重新生成布局灿里,重新排列元素。
以下情況會(huì)引發(fā)重排:
- 頁(yè)面初始渲染(無(wú)法避免)
- 添加或刪除可見(jiàn)的DOM元素
- 元素位置的改變昧识,或者使用動(dòng)畫(huà)
- 改變?cè)爻叽缒扑模热邕吘嗟涟恰⑻畛涔蚶恪⑦吙颉挾群透叨鹊?/li>
- 填充內(nèi)容的改變侣灶,比如文本的改變或圖片大小改變而引起的計(jì)算值寬度和高度的改變
- 瀏覽器窗口尺寸的變化(resize事件發(fā)生時(shí))
- 設(shè)置 style 屬性的值甸祭,因?yàn)橥ㄟ^(guò)設(shè)置style屬性改變結(jié)點(diǎn)樣式的話,每一次設(shè)置都會(huì)觸發(fā)一次reflow
- 讀取某些元素屬性:offsetLeft/Top/Height/Width, clientTop/Left/Width/Height, scrollTop/Left/Width/Height, width/height, getComputedStyle(), currentStyle(IE)
重排影響的范圍:
由于瀏覽器渲染界面是基于流失布局模型的褥影,所以觸發(fā)重排時(shí)會(huì)對(duì)周?chē)鶧OM重新排列池户,影響的范圍有兩種:
- 全局范圍:從根節(jié)點(diǎn)html開(kāi)始對(duì)整個(gè)渲染樹(shù)進(jìn)行重新布局
- 局部范圍:對(duì)渲染樹(shù)的某部分或某一個(gè)渲染對(duì)象進(jìn)行重新布局
局部布局來(lái)解釋這種現(xiàn)象:把一個(gè)dom的寬高之類(lèi)的幾何信息定死,然后在dom內(nèi)部觸發(fā)重排凡怎,就只會(huì)重新渲染該dom內(nèi)部的元素校焦,而不會(huì)影響到外界。
重繪(Repaints):
當(dāng)一個(gè)元素的外觀發(fā)生改變统倒,但沒(méi)有改變布局, 瀏覽器重新把元素外觀繪制出來(lái)的過(guò)程寨典,叫做重繪。
重排必定會(huì)引發(fā)重繪房匆,但重繪不一定會(huì)引發(fā)重排耸成。
代價(jià)
重排的代價(jià)是高昂的,會(huì)破壞用戶體驗(yàn)浴鸿,并且讓UI展示非常遲緩井氢,重排重繪非常耗費(fèi)資源,是導(dǎo)致網(wǎng)頁(yè)性能低下的根本原因岳链。
DOM變動(dòng)和樣式變動(dòng)花竞,都會(huì)觸發(fā)重新渲染。但是掸哑,瀏覽器已經(jīng)很智能了约急,會(huì)盡量把所有的變動(dòng)集中在一起,排成一個(gè)隊(duì)列举户,然后一次性執(zhí)行烤宙,盡量避免多次重新渲染
// div元素有兩個(gè)樣式變動(dòng),但是瀏覽器只會(huì)觸發(fā)一次重排和重繪
div.style.color = 'blue';
div.style.marginTop = '30px';
如果寫(xiě)得不好俭嘁,就會(huì)觸發(fā)兩次重排和重繪
// 對(duì)div元素設(shè)置背景色以后躺枕,第二行要求瀏覽器給出該元素的位置,所以瀏覽器不得不立即重排
div.style.color = 'blue';
var margin = parseInt(div.style.marginTop);
div.style.marginTop = (margin + 10) + 'px';
優(yōu)化
我們應(yīng)盡量減少重排,最簡(jiǎn)單的方式是:1.減少重排次數(shù)拐云;2.縮小重排范圍
-
減少重排范圍
- 應(yīng)該盡量以局部布局的形式組織HTML結(jié)構(gòu)罢猪,使各個(gè)結(jié)構(gòu)間相互獨(dú)立,當(dāng)某個(gè)結(jié)構(gòu)發(fā)生重排時(shí)叉瘩,不會(huì)影響到頁(yè)面上的其它結(jié)構(gòu)
- 應(yīng)該盡可能在底層級(jí)的元素上設(shè)置樣式膳帕,削弱修改樣式時(shí),對(duì)頁(yè)面其它元素帶來(lái)影響
- 不要使用table布局薇缅,可能很小的一個(gè)小改動(dòng)會(huì)造成整個(gè)table的重新布局
-
減少重排次數(shù)
-
不要頻繁的操作樣式危彩,對(duì)于一個(gè)靜態(tài)頁(yè)面來(lái)說(shuō),明智且可維護(hù)的做法是更改類(lèi)名而不是修改樣式泳桦,對(duì)于動(dòng)態(tài)改變的樣式來(lái)說(shuō)汤徽,相較每次微小修改都直接觸及元素,更好的辦法是統(tǒng)一在
cssText
變量中編輯灸撰。// bad var left = 10; var top = 10; el.style.left = left + "px"; el.style.top = top + "px"; // good el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
-
離線操作元素:
-
使用display:none
一旦我們給元素設(shè)置
display:none
時(shí)(只有一次重排重繪)谒府,元素便不會(huì)再存在在渲染樹(shù)中,相當(dāng)于將其從頁(yè)面上“拿掉”浮毯,我們之后的操作將不會(huì)觸發(fā)重排和重繪完疫,添加足夠多的變更后,通過(guò)display
屬性顯示(另一次重排重繪)债蓝。通過(guò)這種方式即使大量變更也只觸發(fā)兩次重排壳鹤。另外,visibility : hidden
的元素只對(duì)重繪有影響惦蚊,不影響重排 通過(guò)
documentFragment
創(chuàng)建一個(gè)dom
碎片,在它上面批量操作dom
器虾,操作完成之后,再添加到文檔中蹦锋,這樣只會(huì)觸發(fā)一次重排-
復(fù)制節(jié)點(diǎn)兆沙,在副本上工作,然后替換它
dom.display = 'none' // 修改dom樣式 dom.display = 'block' var fragment = document.createDocumentFragment() // 操作dom document.body.appendChild(fragment); var newUL = oUl.cloneNode(true); // 操作dom parentElement.replaceChild(newUl, oUl);
-
-
使用 absolute 或 fixed 脫離文檔流:使用
absolute
或fixed
脫離文檔流使用絕對(duì)定位會(huì)使的該元素單獨(dú)成為渲染樹(shù)中body
的一個(gè)子元素莉掂,重排開(kāi)銷(xiāo)比較小葛圃,不會(huì)對(duì)其它節(jié)點(diǎn)造成太多影響-
分離讀寫(xiě)操作:DOM 的多個(gè)讀操作(或多個(gè)寫(xiě)操作),應(yīng)該放在一起憎妙。不要兩個(gè)讀操作之間库正,加入一個(gè)寫(xiě)操作
// bad 強(qiáng)制刷新 觸發(fā)四次重排+重繪 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 緩存布局信息 相當(dāng)于讀寫(xiě)分離 觸發(fā)一次重排+重繪 var curLeft = div.offsetLeft; var curTop = div.offsetTop; var curRight = div.offsetRight; var curBottom = div.offsetBottom;
觀察
我們可以通過(guò)Chrome觀察重排與重繪
打開(kāi)開(kāi)發(fā)者工具 => 點(diǎn)擊Performance => 點(diǎn)擊左側(cè)小圓點(diǎn) => 點(diǎn)擊刷新頁(yè)面 => 錄制=>查看數(shù)據(jù)
- Loading: 網(wǎng)絡(luò)通信和HTML解析
- Scripting: JavaScript執(zhí)行
- Rendering: 樣式計(jì)算和布局,即重排
- Painting: 重繪