1紊浩、瀏覽器渲染過程
從上面這個圖上旭等,我們可以看到酌呆,瀏覽器渲染過程如下:
- 解析HTML,生成DOM樹搔耕,解析CSS隙袁,生成CSSOM樹
- 將DOM樹和CSSOM樹結合,生成渲染樹(Render Tree)
- Layout(回流):根據(jù)生成的渲染樹,進行回流(Layout)菩收,得到節(jié)點的幾何信息(位置梨睁,大小)
- Painting(重繪):根據(jù)渲染樹以及回流得到的幾何信息娜饵,得到節(jié)點的絕對像素
- Display:將像素發(fā)送給GPU坡贺,展示在頁面上。(這一步其實還有很多內(nèi)容箱舞,比如會在GPU將多個合成層合并為同一個層遍坟,并展示在頁面中。而css3硬件加速的原理則是新建合成層褐缠,這里我們不展開政鼠,之后有機會會寫一篇博客)
渲染過程看起來很簡單,讓我們來具體了解下每一步具體做了什么队魏。
生成渲染樹
為了構建渲染樹,瀏覽器主要完成了以下工作:
- 從DOM樹的根節(jié)點開始遍歷每個可見節(jié)點万搔。
- 對于每個可見的節(jié)點胡桨,找到CSSOM樹中對應的規(guī)則,并應用它們瞬雹。
- 根據(jù)每個可見節(jié)點以及其對應的樣式昧谊,組合生成渲染樹。
第一步中酗捌,既然說到了要遍歷可見的節(jié)點呢诬,那么我們得先知道,什么節(jié)點是不可見的胖缤。不可見的節(jié)點包括:
- 一些不會渲染輸出的節(jié)點尚镰,比如script、meta哪廓、link等狗唉。
- 一些通過css進行隱藏的節(jié)點。比如display:none涡真。注意分俯,利用visibility和opacity隱藏的節(jié)點,還是會顯示在渲染樹上的哆料。只有display:none的節(jié)點才不會顯示在渲染樹上缸剪。
注意:渲染樹只包含可見的節(jié)點
2、回流與重繪
1东亦、概述
- 回流:當render tree中的一部分(或全部)杏节,因為元素的規(guī)模尺寸、布局、隱藏等改變而需要重新構建拢锹,這就是回流(reflow)
- 重繪:回流完成后谣妻,瀏覽器會重新繪制受影響的部分,這就是重繪過程卒稳。
當render tree中的一些元素需要更新屬性蹋半,而這些屬性只是影響元素的外觀、風格充坑,而不影響布局减江,則稱為重繪(repaints)
2、何時發(fā)生回流重繪
當頁面布局和幾何屬性改變時就需要回流
- 添加或刪除可見的DOM元素
- 元素的位置發(fā)生變化(offsetWidth捻爷、offsetHeight)
- 元素尺寸發(fā)生變化(包括外邊距辈灼、內(nèi)邊框、邊框大小也榄、高度和寬度等)
- 內(nèi)容發(fā)生變化(文本巡莹、圖片、input框)
- 頁面渲染初始化
- 瀏覽器窗口尺寸改變(回流是根據(jù)視口的大小來計算元素的位置和大小的)
- 增加或移除樣式表
- 操作class屬性
- 改變字體
- 激活偽類(如:hover)
注意:回流一定會觸發(fā)重繪甜紫,而重繪不一定會回流
(比如在body最前面插入一個元素降宅,會導致整個render tree回流,如果在body后面插入一個元素囚霸,則不會影響前面元素的回流)
3腰根、瀏覽器的優(yōu)化機制
現(xiàn)代的瀏覽器都是很聰明的,由于每次重排都會造成額外的計算消耗拓型,因此大多數(shù)瀏覽器都會通過隊列化修改并批量執(zhí)行來優(yōu)化重排過程额嘿。瀏覽器會將修改操作放入到隊列里,直到過了一段時間或者操作達到了一個閾值劣挫,才清空隊列册养。但是!當你獲取布局信息的操作的時候揣云,會強制隊列刷新捕儒,比如當你訪問以下屬性或者使用以下方法:
- offsetTop、offsetLeft邓夕、offsetWidth刘莹、offsetHeight
- scrollTop、scrollLeft焚刚、scrollWidth点弯、scrollHeight
- clientTop、clientLeft矿咕、clientWidth抢肛、clientHeight
- getComputedStyle() 或者 IE中:currentStyle
- getBoundingClientRect
- 具體可以訪問這個網(wǎng)站:https://gist.github.com/pauli...
以上屬性和方法都需要返回最新的布局信息狼钮,因此瀏覽器不得不清空隊列,觸發(fā)回流重繪來返回正確的值捡絮。因此熬芜,我們在修改樣式的時候,最好避免使用上面列出的屬性福稳,他們都會刷新渲染隊列涎拉。如果要使用它們,最好將值緩存起來的圆。
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
當我們需要對DOM對一系列修改的時候梅掠,可以通過以下步驟減少回流重繪次數(shù):
- 使元素脫離文檔流
- 對其進行多次修改
- 將元素帶回到文檔中酌住。
該過程的第一步和第三步可能會引起回流,但是經(jīng)過第一步之后阎抒,對DOM的所有修改都不會引起回流赂韵,因為它已經(jīng)不在渲染樹了。
有三種方式可以讓DOM脫離文檔流:
- 隱藏元素挠蛉,應用修改,重新顯示
- 使用文檔片段(document fragment)在當前DOM之外構建一個子樹肄满,再把它拷貝回文檔谴古。
- 將原始元素拷貝到一個脫離文檔的節(jié)點中,修改節(jié)點后稠歉,再替換原始的元素掰担。
3、避免觸發(fā)同步布局事件
當我們訪問元素的一些屬性的時候怒炸,會導致瀏覽器強制清空隊列带饱,進行強制同步布局。舉個例子阅羹,比如說我們想將一個p標簽數(shù)組的寬度賦值為一個元素的寬度勺疼,我們可能寫出這樣的代碼:
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}
這段代碼看上去是沒有什么問題,可是其實會造成很大的性能問題捏鱼。在每次循環(huán)的時候执庐,都讀取了box的一個offsetWidth屬性值,然后利用它來更新p標簽的width屬性导梆。這就導致了每一次循環(huán)的時候轨淌,瀏覽器都必須先使上一次循環(huán)中的樣式更新操作生效迂烁,才能響應本次循環(huán)的樣式讀取操作。每一次循環(huán)都會強制瀏覽器刷新隊列递鹉。我們可以優(yōu)化為:
const width = box.offsetWidth;
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px';
}
}
4盟步、對于復雜動畫效果,使用絕對定位讓其脫離文檔流
對于復雜動畫效果,由于會經(jīng)常的引起回流重繪躏结,因此却盘,我們可以使用絕對定位,讓它脫離文檔流窜觉。否則會引起父元素以及后續(xù)元素頻繁的回流谷炸。例子
5、css3硬件加速(GPU加速)
使用css3硬件加速禀挫,可以讓transform旬陡、opacity、filters這些動畫不會引起回流重繪 语婴。但是對于動畫的其它屬性描孟,比如background-color這些,還是會引起回流重繪的砰左,不過它還是可以提升這些動畫的性能匿醒。
如何使用
- transform
- opacity
- filters
- Will-change
效果
我們可以先看個例子。我通過使用chrome的Performance捕獲了一段時間的回流重繪情況缠导,實際結果如下圖:
從圖中我們可以看出廉羔,在動畫進行的時候,沒有發(fā)生任何的回流重繪僻造。
重點
- 使用css3硬件加速憋他,可以讓transform、opacity髓削、filters這些動畫不會引起回流重繪
- 對于動畫的其它屬性竹挡,比如background-color這些,還是會引起回流重繪的立膛,不過它還是可以提升這些動畫的性能揪罕。
css3硬件加速的坑
- 如果你為太多元素使用css3硬件加速,會導致內(nèi)存占用較大宝泵,會有性能問題好啰。
- 在GPU渲染字體會導致抗鋸齒無效。這是因為GPU和CPU的算法不同鲁猩。因此如果你不在動畫結束的時候關閉硬件加速坎怪,會產(chǎn)生字體模糊。