1、瀏覽器渲染過程

從上面這個圖上,我們可以看到,瀏覽器渲染過程如下:
- 解析HTML,生成DOM樹,解析CSS,生成CSSOM樹
- 將DOM樹和CSSOM樹結(jié)合,生成渲染樹(Render Tree)
- Layout(回流):根據(jù)生成的渲染樹,進(jìn)行回流(Layout),得到節(jié)點(diǎn)的幾何信息(位置,大?。?/li>
- Painting(重繪):根據(jù)渲染樹以及回流得到的幾何信息,得到節(jié)點(diǎn)的絕對像素
- Display:將像素發(fā)送給GPU,展示在頁面上。(這一步其實(shí)還有很多內(nèi)容,比如會在GPU將多個合成層合并為同一個層,并展示在頁面中。而css3硬件加速的原理則是新建合成層,這里我們不展開,之后有機(jī)會會寫一篇博客)
渲染過程看起來很簡單,讓我們來具體了解下每一步具體做了什么。
生成渲染樹

為了構(gòu)建渲染樹,瀏覽器主要完成了以下工作:
- 從DOM樹的根節(jié)點(diǎn)開始遍歷每個可見節(jié)點(diǎn)。
- 對于每個可見的節(jié)點(diǎn),找到CSSOM樹中對應(yīng)的規(guī)則,并應(yīng)用它們。
- 根據(jù)每個可見節(jié)點(diǎn)以及其對應(yīng)的樣式,組合生成渲染樹。
第一步中,既然說到了要遍歷可見的節(jié)點(diǎn),那么我們得先知道,什么節(jié)點(diǎn)是不可見的。不可見的節(jié)點(diǎn)包括:
- 一些不會渲染輸出的節(jié)點(diǎn),比如script、meta、link等。
- 一些通過css進(jìn)行隱藏的節(jié)點(diǎn)。比如display:none。注意,利用visibility和opacity隱藏的節(jié)點(diǎn),還是會顯示在渲染樹上的。只有display:none的節(jié)點(diǎn)才不會顯示在渲染樹上。
注意:渲染樹只包含可見的節(jié)點(diǎn)
2、回流與重繪
1、概述
- 回流:當(dāng)render tree中的一部分(或全部),因?yàn)樵氐囊?guī)模尺寸、布局、隱藏等改變而需要重新構(gòu)建,這就是回流(reflow)
- 重繪:回流完成后,瀏覽器會重新繪制受影響的部分,這就是重繪過程。
當(dāng)render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀、風(fēng)格,而不影響布局,則稱為重繪(repaints)
2、何時發(fā)生回流重繪
當(dāng)頁面布局和幾何屬性改變時就需要回流
- 添加或刪除可見的DOM元素
- 元素的位置發(fā)生變化(offsetWidth、offsetHeight)
- 元素尺寸發(fā)生變化(包括外邊距、內(nèi)邊框、邊框大小、高度和寬度等)
- 內(nèi)容發(fā)生變化(文本、圖片、input框)
- 頁面渲染初始化
- 瀏覽器窗口尺寸改變(回流是根據(jù)視口的大小來計(jì)算元素的位置和大小的)
- 增加或移除樣式表
- 操作class屬性
- 改變字體
- 激活偽類(如:hover)
注意:回流一定會觸發(fā)重繪,而重繪不一定會回流
(比如在body最前面插入一個元素,會導(dǎo)致整個render tree回流,如果在body后面插入一個元素,則不會影響前面元素的回流)
3、瀏覽器的優(yōu)化機(jī)制
現(xiàn)代的瀏覽器都是很聰明的,由于每次重排都會造成額外的計(jì)算消耗,因此大多數(shù)瀏覽器都會通過隊(duì)列化修改并批量執(zhí)行來優(yōu)化重排過程。瀏覽器會將修改操作放入到隊(duì)列里,直到過了一段時間或者操作達(dá)到了一個閾值,才清空隊(duì)列。但是!當(dāng)你獲取布局信息的操作的時候,會強(qiáng)制隊(duì)列刷新,比如當(dāng)你訪問以下屬性或者使用以下方法:
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyle() 或者 IE中:currentStyle
- getBoundingClientRect
- 具體可以訪問這個網(wǎng)站:https://gist.github.com/pauli...
以上屬性和方法都需要返回最新的布局信息,因此瀏覽器不得不清空隊(duì)列,觸發(fā)回流重繪來返回正確的值。因此,我們在修改樣式的時候,最好避免使用上面列出的屬性,他們都會刷新渲染隊(duì)列。如果要使用它們,最好將值緩存起來。
3、如何減少回流重繪
1、最小化重繪
減少對render tree的操作,并減少一些對style信息的請求,合理利用瀏覽器的優(yōu)化策略
const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
- 使用cssText
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
- 修改CSS的classname
const el = document.getElementById('test');
el.className += ' active';
2、批量處理DOM
當(dāng)我們需要對DOM對一系列修改的時候,可以通過以下步驟減少回流重繪次數(shù):
- 使元素脫離文檔流
- 對其進(jìn)行多次修改
- 將元素帶回到文檔中。
該過程的第一步和第三步可能會引起回流,但是經(jīng)過第一步之后,對DOM的所有修改都不會引起回流,因?yàn)樗呀?jīng)不在渲染樹了。
有三種方式可以讓DOM脫離文檔流:
- 隱藏元素,應(yīng)用修改,重新顯示
- 使用文檔片段(document fragment)在當(dāng)前DOM之外構(gòu)建一個子樹,再把它拷貝回文檔。
- 將原始元素拷貝到一個脫離文檔的節(jié)點(diǎn)中,修改節(jié)點(diǎn)后,再替換原始的元素。
3、避免觸發(fā)同步布局事件
當(dāng)我們訪問元素的一些屬性的時候,會導(dǎo)致瀏覽器強(qiáng)制清空隊(duì)列,進(jìn)行強(qiáng)制同步布局。舉個例子,比如說我們想將一個p標(biāo)簽數(shù)組的寬度賦值為一個元素的寬度,我們可能寫出這樣的代碼:
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}
這段代碼看上去是沒有什么問題,可是其實(shí)會造成很大的性能問題。在每次循環(huán)的時候,都讀取了box的一個offsetWidth屬性值,然后利用它來更新p標(biāo)簽的width屬性。這就導(dǎo)致了每一次循環(huán)的時候,瀏覽器都必須先使上一次循環(huán)中的樣式更新操作生效,才能響應(yīng)本次循環(huán)的樣式讀取操作。每一次循環(huán)都會強(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';
}
}
4、對于復(fù)雜動畫效果,使用絕對定位讓其脫離文檔流
對于復(fù)雜動畫效果,由于會經(jīng)常的引起回流重繪,因此,我們可以使用絕對定位,讓它脫離文檔流。否則會引起父元素以及后續(xù)元素頻繁的回流。例子
5、css3硬件加速(GPU加速)
使用css3硬件加速,可以讓transform、opacity、filters這些動畫不會引起回流重繪 。但是對于動畫的其它屬性,比如background-color這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。
如何使用
- transform
- opacity
- filters
- Will-change
效果
我們可以先看個例子。我通過使用chrome的Performance捕獲了一段時間的回流重繪情況,實(shí)際結(jié)果如下圖:

從圖中我們可以看出,在動畫進(jìn)行的時候,沒有發(fā)生任何的回流重繪。
重點(diǎn)
- 使用css3硬件加速,可以讓transform、opacity、filters這些動畫不會引起回流重繪
- 對于動畫的其它屬性,比如background-color這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。
css3硬件加速的坑
- 如果你為太多元素使用css3硬件加速,會導(dǎo)致內(nèi)存占用較大,會有性能問題。
- 在GPU渲染字體會導(dǎo)致抗鋸齒無效。這是因?yàn)镚PU和CPU的算法不同。因此如果你不在動畫結(jié)束的時候關(guān)閉硬件加速,會產(chǎn)生字體模糊。