摘要:否則會(huì)引起父元素以及后續(xù)元素頻繁的回流。硬件加速加速硬件加速加速比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個(gè)時(shí)候,硬件加速就閃亮登場(chǎng)啦劃重點(diǎn)使用硬件加速,可以讓這些動(dòng)畫不會(huì)引起回流重繪。
回流和重繪可以說(shuō)是每一個(gè)web開發(fā)者都經(jīng)常聽到的兩個(gè)詞語(yǔ),我也不例外,可是一直不是很清楚這兩步具體做了什么事情。最近由于部門內(nèi)部要做分享,所以對(duì)其進(jìn)行了一些研究,看了一些博客和書籍,整理了一些內(nèi)容并且結(jié)合自己的體會(huì),寫了這篇文章,希望可以幫助到大家。
本文先從瀏覽器的渲染過(guò)程來(lái)從頭到尾的講解一下回流重繪,如果大家想直接看如何減少回流和重繪,可以跳到后面。(這個(gè)渲染過(guò)程來(lái)自MDN)
從上面這個(gè)圖上,我們可以看到,瀏覽器渲染過(guò)程如下:
渲染過(guò)程看起來(lái)很簡(jiǎn)單,讓我們來(lái)具體了解下每一步具體做了什么。
為了構(gòu)建渲染樹,瀏覽器主要完成了以下工作:
第一步中,既然說(shuō)到了要遍歷可見的節(jié)點(diǎn),那么我們得先知道,什么節(jié)點(diǎn)是不可見的。不可見的節(jié)點(diǎn)包括:
注意:渲染樹只包含可見的節(jié)點(diǎn)
前面我們通過(guò)構(gòu)造渲染樹,我們將可見DOM節(jié)點(diǎn)以及它對(duì)應(yīng)的樣式結(jié)合起來(lái),可是我們還需要計(jì)算它們?cè)谠O(shè)備視口(viewport)內(nèi)的確切位置和大小,這個(gè)計(jì)算的階段就是回流。
為了弄清每個(gè)對(duì)象在網(wǎng)站上的確切大小和位置,瀏覽器從渲染樹的根節(jié)點(diǎn)開始遍歷,我們可以以下面這個(gè)實(shí)例來(lái)表示:
Critial Path: Hello world!
Hello world!
我們可以看到,第一個(gè)div將節(jié)點(diǎn)的顯示尺寸設(shè)置為視口寬度的50%,第二個(gè)div將其尺寸設(shè)置為父節(jié)點(diǎn)的50%。而在回流這個(gè)階段,我們就需要根據(jù)視口具體的寬度,將其轉(zhuǎn)為實(shí)際的像素值。(如下圖)
最終,我們通過(guò)構(gòu)造渲染樹和回流階段,我們知道了哪些節(jié)點(diǎn)是可見的,以及可見節(jié)點(diǎn)的樣式和具體的幾何信息(位置、大小),那么我們就可以將渲染樹的每個(gè)節(jié)點(diǎn)都轉(zhuǎn)換為屏幕上的實(shí)際像素,這個(gè)階段就叫做重繪節(jié)點(diǎn)。
既然知道了瀏覽器的渲染過(guò)程后,我們就來(lái)探討下,何時(shí)會(huì)發(fā)生回流重繪。
我們前面知道了,回流這一階段主要是計(jì)算節(jié)點(diǎn)的位置和幾何信息,那么當(dāng)頁(yè)面布局和幾何信息發(fā)生變化的時(shí)候,就需要回流。比如以下情況:
注意:回流一定會(huì)觸發(fā)重繪,而重繪不一定會(huì)回流
根據(jù)改變的范圍和程度,渲染樹中或大或小的部分需要重新計(jì)算,有些改變會(huì)觸發(fā)整個(gè)頁(yè)面的重排,比如,滾動(dòng)條出現(xiàn)的時(shí)候或者修改了根節(jié)點(diǎn)。
現(xiàn)代的瀏覽器都是很聰明的,由于每次重排都會(huì)造成額外的計(jì)算消耗,因此大多數(shù)瀏覽器都會(huì)通過(guò)隊(duì)列化修改并批量執(zhí)行來(lái)優(yōu)化重排過(guò)程。瀏覽器會(huì)將修改操作放入到隊(duì)列里,直到過(guò)了一段時(shí)間或者操作達(dá)到了一個(gè)閾值,才清空隊(duì)列。但是!當(dāng)你獲取布局信息的操作的時(shí)候,會(huì)強(qiáng)制隊(duì)列刷新,比如當(dāng)你訪問(wèn)以下屬性或者使用以下方法:
以上屬性和方法都需要返回最新的布局信息,因此瀏覽器不得不清空隊(duì)列,觸發(fā)回流重繪來(lái)返回正確的值。因此,我們?cè)谛薷臉邮降臅r(shí)候,最好避免使用上面列出的屬性,他們都會(huì)刷新渲染隊(duì)列。如果要使用它們,最好將值緩存起來(lái)。
好了,到了我們今天的重頭戲,前面說(shuō)了這么多背景和理論知識(shí),接下來(lái)讓我們談?wù)勅绾螠p少回流和重繪。
由于重繪和重排可能代價(jià)比較昂貴,因此最好就是可以減少它的發(fā)生次數(shù)。為了減少發(fā)生次數(shù),我們可以合并多次對(duì)DOM和樣式的修改,然后一次處理掉。考慮這個(gè)例子
const el = document.getElementById("test");
el.style.padding = "5px";
el.style.borderLeft = "1px";
el.style.borderRight = "2px";
例子中,有三個(gè)樣式屬性被修改了,每一個(gè)都會(huì)影響元素的幾何結(jié)構(gòu),引起回流。當(dāng)然,大部分現(xiàn)代瀏覽器都對(duì)其做了優(yōu)化,因此,只會(huì)觸發(fā)一次重排。但是如果在舊版的瀏覽器或者在上面代碼執(zhí)行的時(shí)候,有其他代碼訪問(wèn)了布局信息(上文中的會(huì)觸發(fā)回流的布局信息),那么就會(huì)導(dǎo)致三次重排。
因此,我們可以合并所有的改變?nèi)缓笠来翁幚恚热缥覀兛梢圆扇∫韵碌姆绞剑?/p>
使用cssText
const el = document.getElementById("test");
el.style.cssText += "border-left: 1px; border-right: 2px; padding: 5px;";
修改CSS的class
const el = document.getElementById("test");
el.className += " active";
當(dāng)我們需要對(duì)DOM對(duì)一系列修改的時(shí)候,可以通過(guò)以下步驟減少回流重繪次數(shù):
該過(guò)程的第一步和第三步可能會(huì)引起回流,但是經(jīng)過(guò)第一步之后,對(duì)DOM的所有修改都不會(huì)引起回流,因?yàn)樗呀?jīng)不在渲染樹了。
有三種方式可以讓DOM脫離文檔流:
考慮我們要執(zhí)行一段批量插入節(jié)點(diǎn)的代碼:
function appendDataToElement(appendToElement, data) {
let li;
for (let i = 0; i < data.length; i++) {
li = document.createElement("li");
li.textContent = "text";
appendToElement.appendChild(li);
}
}
const ul = document.getElementById("list");
appendDataToElement(ul, data);
如果我們直接這樣執(zhí)行的話,由于每次循環(huán)都會(huì)插入一個(gè)新的節(jié)點(diǎn),會(huì)導(dǎo)致瀏覽器回流一次。
我們可以使用這三種方式進(jìn)行優(yōu)化:
隱藏元素,應(yīng)用修改,重新顯示
這個(gè)會(huì)在展示和隱藏節(jié)點(diǎn)的時(shí)候,產(chǎn)生兩次重繪
function appendDataToElement(appendToElement, data) {
let li;
for (let i = 0; i < data.length; i++) {
li = document.createElement("li");
li.textContent = "text";
appendToElement.appendChild(li);
}
}
const ul = document.getElementById("list");
ul.style.display = "none";
appendDataToElement(ul, data);
ul.style.display = "block";
使用文檔片段(document fragment)在當(dāng)前DOM之外構(gòu)建一個(gè)子樹,再把它拷貝回文檔
const ul = document.getElementById("list");
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
ul.appendChild(fragment);
將原始元素拷貝到一個(gè)脫離文檔的節(jié)點(diǎn)中,修改節(jié)點(diǎn)后,再替換原始的元素。
const ul = document.getElementById("list");
const clone = ul.cloneNode(true);
appendDataToElement(clone, data);
ul.parentNode.replaceChild(clone, ul);
對(duì)于上述那種情況,我寫了一個(gè)demo來(lái)測(cè)試修改前和修改后的性能。然而實(shí)驗(yàn)結(jié)果不是很理想。
原因:原因其實(shí)上面也說(shuō)過(guò)了,瀏覽器會(huì)使用隊(duì)列來(lái)儲(chǔ)存多次修改,進(jìn)行優(yōu)化,所以對(duì)這個(gè)優(yōu)化方案,我們其實(shí)不用優(yōu)先考慮。
上文我們說(shuō)過(guò),當(dāng)我們?cè)L問(wèn)元素的一些屬性的時(shí)候,會(huì)導(dǎo)致瀏覽器強(qiáng)制清空隊(duì)列,進(jìn)行強(qiáng)制同步布局。舉個(gè)例子,比如說(shuō)我們想將一個(gè)p標(biāo)簽數(shù)組的寬度賦值為一個(gè)元素的寬度,我們可能寫出這樣的代碼:
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + "px";
}
}
這段代碼看上去是沒(méi)有什么問(wèn)題,可是其實(shí)會(huì)造成很大的性能問(wèn)題。在每次循環(huán)的時(shí)候,都讀取了box的一個(gè)offsetWidth屬性值,然后利用它來(lái)更新p標(biāo)簽的width屬性。這就導(dǎo)致了每一次循環(huán)的時(shí)候,瀏覽器都必須先使上一次循環(huán)中的樣式更新操作生效,才能響應(yīng)本次循環(huán)的樣式讀取操作。每一次循環(huán)都會(huì)強(qiáng)制瀏覽器刷新隊(duì)列。我們可以優(yōu)化為:
const width = box.offsetWidth;
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + "px";
}
}
同樣,我也寫了個(gè)demo來(lái)比較兩者的性能差異。你可以自己點(diǎn)開這個(gè)demo體驗(yàn)下。這個(gè)對(duì)比差距就比較明顯。
對(duì)于復(fù)雜動(dòng)畫效果,由于會(huì)經(jīng)常的引起回流重繪,因此,我們可以使用絕對(duì)定位,讓它脫離文檔流。否則會(huì)引起父元素以及后續(xù)元素頻繁的回流。這個(gè)我們就直接上個(gè)例子。
打開這個(gè)例子后,我們可以打開控制臺(tái),控制臺(tái)上會(huì)輸出當(dāng)前的幀數(shù)(雖然不準(zhǔn))。
從上圖中,我們可以看到,幀數(shù)一直都沒(méi)到60。這個(gè)時(shí)候,只要我們點(diǎn)擊一下那個(gè)按鈕,把這個(gè)元素設(shè)置為絕對(duì)定位,幀數(shù)就可以穩(wěn)定60。
比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個(gè)時(shí)候,css3硬件加速就閃亮登場(chǎng)啦??!
劃重點(diǎn):使用css3硬件加速,可以讓transform、opacity、filters這些動(dòng)畫不會(huì)引起回流重繪 。但是對(duì)于動(dòng)畫的其它屬性,比如background-color這些,還是會(huì)引起回流重繪的,不過(guò)它還是可以提升這些動(dòng)畫的性能。
本篇文章只討論如何使用,暫不考慮其原理,之后有空會(huì)另外開篇文章說(shuō)明。
常見的觸發(fā)硬件加速的css屬性:
我們可以先看個(gè)例子。我通過(guò)使用chrome的Performance捕獲了一段時(shí)間的回流重繪情況,實(shí)際結(jié)果如下圖:
從圖中我們可以看出,在動(dòng)畫進(jìn)行的時(shí)候,沒(méi)有發(fā)生任何的回流重繪。如果感興趣你也可以自己做下實(shí)驗(yàn)。
如果你為太多元素使用css3硬件加速,會(huì)導(dǎo)致內(nèi)存占用較大,會(huì)有性能問(wèn)題。
在GPU渲染字體會(huì)導(dǎo)致抗鋸齒無(wú)效。這是因?yàn)镚PU和CPU的算法不同。因此如果你不在動(dòng)畫結(jié)束的時(shí)候關(guān)閉硬件加速,會(huì)產(chǎn)生字體模糊。
本文主要講了瀏覽器的渲染過(guò)程、瀏覽器的優(yōu)化機(jī)制以及如何減少甚至避免回流和重繪,希望可以幫助大家更好的理解回流重繪。
本文地址在->本人博客地址, 歡迎給個(gè) start 或 follow
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/1455.html
摘要:對(duì)于復(fù)雜動(dòng)畫效果使用絕對(duì)定位讓其脫離文檔流對(duì)于復(fù)雜動(dòng)畫效果,由于會(huì)經(jīng)常的引起回流重繪,因此,我們可以使用絕對(duì)定位,讓它脫離文檔流。硬件加速加速比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。 回流和重繪可以說(shuō)是每一個(gè)web開發(fā)者都經(jīng)常聽到的兩個(gè)詞語(yǔ),我也不例外,可是我之前一直不是很清楚這兩步具體做了什么事情。最近由于部門內(nèi)部要做分享,所以對(duì)其進(jìn)行了一些研究,看了一些博客和書...
摘要:硬件加速加速比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個(gè)時(shí)候,硬件加速就閃亮登場(chǎng)啦劃重點(diǎn)使用硬件加速,可以讓這些動(dòng)畫不會(huì)引起回流重繪。 本文由云+社區(qū)發(fā)表 回流和重繪可以說(shuō)是每一個(gè)web開發(fā)者都經(jīng)常聽到的兩個(gè)詞語(yǔ),可是可能有很多人不是很清楚這兩步具體做了什么事情。最近有空對(duì)其進(jìn)行了一些研究,看了一些博客和書籍,整理了一些內(nèi)容并且結(jié)合一些例子,寫了這篇文章,希望可以幫...
摘要:硬件加速加速比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個(gè)時(shí)候,硬件加速就閃亮登場(chǎng)啦劃重點(diǎn)使用硬件加速,可以讓這些動(dòng)畫不會(huì)引起回流重繪。 本文由云+社區(qū)發(fā)表 回流和重繪可以說(shuō)是每一個(gè)web開發(fā)者都經(jīng)常聽到的兩個(gè)詞語(yǔ),可是可能有很多人不是很清楚這兩步具體做了什么事情。最近有空對(duì)其進(jìn)行了一些研究,看了一些博客和書籍,整理了一些內(nèi)容并且結(jié)合一些例子,寫了這篇文章,希望可以幫...
摘要:回流也被稱為重排,其實(shí)從字面上來(lái)看,重排更容易讓人形象易懂即重新排版整個(gè)頁(yè)面。重繪當(dāng)頁(yè)面元素樣式改變不影響元素在文檔流中的位置時(shí)如,,,瀏覽器只會(huì)將新樣式賦予元素并進(jìn)行重新繪制操作。你真的了解回流和重繪嗎 簡(jiǎn)單先了解一下瀏覽器的渲染過(guò)程(圖片來(lái)自于網(wǎng)絡(luò)) showImg(https://segmentfault.com/img/bVbaC2e?w=624&h=289); 瀏覽器生成渲染...
閱讀 3807·2021-11-17 09:33
閱讀 2025·2021-10-26 09:51
閱讀 1541·2021-09-29 09:44
閱讀 1693·2019-08-30 15:55
閱讀 1457·2019-08-30 15:52
閱讀 2337·2019-08-30 15:43
閱讀 3444·2019-08-29 17:00
閱讀 2311·2019-08-29 16:23