首先,我們應(yīng)該知道,瀏覽器會(huì)解析三個(gè)東西:
- 一個(gè)是HTML/SVG/XHTML,事實(shí)上,Webkit有三個(gè)C++的類對(duì)應(yīng)這三類文檔。解析這三種文件會(huì)產(chǎn)生一個(gè)DOM Tree。
- CSS,解析CSS會(huì)產(chǎn)生CSS規(guī)則樹(shù)。
- Javascript,腳本,主要是通過(guò)DOM API和CSSOM API來(lái)操作DOM Tree和CSS Rule Tree。
然后解析完成之后,瀏覽器引擎會(huì)通過(guò)DOM Tree 和 CSS Rule Tree 來(lái)構(gòu)造 Rendering Tree。注意:
- Rendering Tree 渲染樹(shù)并不等同于DOM樹(shù),因?yàn)橐恍┫馠eader或display:none的東西就沒(méi)必要放在渲染樹(shù)中了。
CSS 的 Rule Tree主要是為了完成匹配并把CSS Rule附加上Rendering Tree上的每個(gè)Element。也就是DOM結(jié)點(diǎn)。也就是所謂的Frame。 - 然后,計(jì)算每個(gè)Frame(也就是每個(gè)Element)的位置,這又叫l(wèi)ayout和reflow過(guò)程。
最后通過(guò)調(diào)用操作系統(tǒng)Native GUI的API繪制。
解析過(guò)程就是下面這個(gè)樣子~
解析完成之后開(kāi)始渲染~
流程大概是這樣子的:
- 計(jì)算CSS樣式
- 構(gòu)建Render Tree
- Layout – 定位坐標(biāo)和大小,是否換行,各種position, overflow, z-index屬性 ……
- 正式開(kāi)畫(huà)
(這是借用酷殼的圖片,具體出處:http://coolshell.cn/articles/9666.html)
上圖流程中有很多連接線,這表示了Javascript動(dòng)態(tài)修改了DOM屬性或是CSS屬會(huì)導(dǎo)致重新Layout,有些改變不會(huì),就是那些指到天上的箭頭,比如,修改后的CSS rule沒(méi)有被匹配到,等。
有一個(gè)概念要講一下:
一個(gè)是Reflow,另一個(gè)是Repaint
- Repaint——屏幕的一部分要重畫(huà),比如某個(gè)CSS的背景色變了。但是元素的幾何尺寸沒(méi)有變。
- Reflow——意味著元件的幾何尺寸變了,我們需要重新驗(yàn)證并計(jì)算Render Tree。是Render Tree的一部分或全部發(fā)生了變化。這就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式布局,所以,如果某元件的幾何尺寸發(fā)生了變化,需要重新布局,也就叫reflow)reflow 會(huì)從<html>這個(gè)root frame開(kāi)始遞歸往下,依次計(jì)算所有的結(jié)點(diǎn)幾何尺寸和位置,在reflow過(guò)程中,可能會(huì)增加一些frame,比如一個(gè)文本字符串必需被包裝起來(lái)。
Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每個(gè)結(jié)點(diǎn)都會(huì)有reflow方法,一個(gè)結(jié)點(diǎn)的reflow很有可能導(dǎo)致子結(jié)點(diǎn),甚至父點(diǎn)以及同級(jí)結(jié)點(diǎn)的reflow。在一些高性能的電腦上也許還沒(méi)什么,但是如果reflow發(fā)生在手機(jī)上,那么這個(gè)過(guò)程是非常痛苦和耗電的。
所以,下面這些動(dòng)作有很大可能會(huì)是成本比較高的。
- 當(dāng)你增加、刪除、修改DOM結(jié)點(diǎn)時(shí),會(huì)導(dǎo)致Reflow或Repaint
- 當(dāng)你移動(dòng)DOM的位置,或是搞個(gè)動(dòng)畫(huà)的時(shí)候。
- 當(dāng)你修改CSS樣式的時(shí)候。
- 當(dāng)你Resize窗口的時(shí)候(移動(dòng)端沒(méi)有這個(gè)問(wèn)題),或是滾動(dòng)的時(shí)候。
- 當(dāng)你修改網(wǎng)頁(yè)的默認(rèn)字體時(shí)。
- 注:display:none會(huì)觸發(fā)reflow,而visibility:hidden只會(huì)觸發(fā)repaint,因?yàn)闆](méi)有發(fā)現(xiàn)位置變化。
多說(shuō)兩句關(guān)于滾屏的事,通常來(lái)說(shuō),如果在滾屏的時(shí)候,我們的頁(yè)面上的所有的像素都會(huì)跟著滾動(dòng),那么性能上沒(méi)什么問(wèn)題,因?yàn)槲覀兊娘@卡對(duì)于這種把全屏像素往上往下移的算法是很快。但是如果你有一個(gè)fixed的背景圖,或是有些Element不跟著滾動(dòng),有些Elment是動(dòng)畫(huà),那么這個(gè)滾動(dòng)的動(dòng)作對(duì)于瀏覽器來(lái)說(shuō)會(huì)是相當(dāng)相當(dāng)痛苦的一個(gè)過(guò)程。你可以看到很多這樣的網(wǎng)頁(yè)在滾動(dòng)的時(shí)候性能有多差。因?yàn)闈L屏也有可能會(huì)造成reflow。
基本上來(lái)說(shuō),reflow有如下的幾個(gè)原因:
- Initial。網(wǎng)頁(yè)初始化的時(shí)候。
- Incremental。一些Javascript在操作DOM Tree時(shí)。
- Resize。其些元件的尺寸變了。
- StyleChange。如果CSS的屬性發(fā)生變化了。
- Dirty。幾個(gè)Incremental的reflow發(fā)生在同一個(gè)frame的子樹(shù)上。
var bstyle = document.body.style; // cache
bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; // 再一次的 reflow 和 repaint
bstyle.color = "#ededed"; // repaint
bstyle.backgroundColor = "#fad"; // repaint
bstyle.fontSize = "2em"; // reflow, repaint
// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));
當(dāng)然,我們的瀏覽器是聰明的,它不會(huì)像上面那樣,你每改一次樣式,它就reflow或repaint一次。一般來(lái)說(shuō),瀏覽器會(huì)把這樣的操作積攢一批,然后做一次reflow,這又叫異步reflow或增量異步reflow。但是有些情況瀏覽器是不會(huì)這么做的,比如:resize窗口,改變了頁(yè)面默認(rèn)的字體,等。對(duì)于這些操作,瀏覽器會(huì)馬上進(jìn)行reflow。
減少reflow/repaint
- 不要一條一條地修改DOM的樣式。與其這樣,建議定義好css的class,然后修改DOM的className。
- 把DOM離線后修改
- 不要把DOM結(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量。不然這會(huì)導(dǎo)致大量地讀寫(xiě)這個(gè)結(jié)點(diǎn)的屬性。
- 盡可能的修改層級(jí)比較低的DOM。當(dāng)然,改變層級(jí)比較底的DOM有可能會(huì)造成大面積的reflow,但是也可能影響范圍很小。
- 為動(dòng)畫(huà)的HTML元件使用fixed或absoult的position,那么修改他們的CSS是不會(huì)reflow的。
- 不要使用table布局。因?yàn)榭赡芎苄〉囊粋€(gè)小改動(dòng)會(huì)造成整個(gè)table的重新布局。