渲染機(jī)制
渲染步驟
瀏覽器的渲染機(jī)制一般分為以下幾個(gè)步驟:
1. 處理 HTML 并構(gòu)建 DOM 樹兄墅。
2. 處理 CSS 構(gòu)建 CSSOM 樹美尸。
3. 將 DOM 與 CSSOM 合并成一個(gè)渲染樹。
4. 根據(jù)渲染樹來布局拓挥,計(jì)算每個(gè)節(jié)點(diǎn)的位置。
5. 調(diào)用 GPU 繪制,合成圖層镜雨,顯示在屏幕上。
注意:
- 在構(gòu)建 CSSOM 樹時(shí)儿捧,會(huì)阻塞渲染荚坞,直至 CSSOM 樹構(gòu)建完成。并且構(gòu)建 CSSOM 樹是一個(gè)十分消耗性能的過程菲盾,所以應(yīng)該盡量保證層級(jí)扁平颓影,減少過度層疊,越是具體的 CSS 選擇器懒鉴,執(zhí)行速度越慢诡挂。
- 當(dāng) HTML 解析到 script 標(biāo)簽時(shí),會(huì)暫停構(gòu)建 DOM临谱, 完成后才會(huì)從暫停的地方重新開始璃俗。也就是說,如果你想首屏渲染的越快悉默,就越不應(yīng)該在首屏就加載 JS 文件旧找。并且 CSS 也會(huì)影響 JS 的執(zhí)行,只有當(dāng)解析完樣式表才會(huì)執(zhí)行 JS麦牺,所以也可以認(rèn)為這種情況下钮蛛,CSS 也會(huì)暫停構(gòu)建 DOM。
Load 和 DOMContentLoaded 區(qū)別
- Load 事件觸發(fā)代表頁面中的 DOM剖膳,CSS魏颓,JS,圖片已經(jīng)全部加載完畢吱晒。
- DOMContentLoaded 事件觸發(fā)代表初始的 HTML 被完全加載和解析甸饱,不需要等待 CSS,JS仑濒,圖片加載叹话。
圖層
一般來說,可以把普通文檔流看成一個(gè)圖層墩瞳。特定的屬性可以生成一個(gè)新的圖層驼壶。不同的圖層渲染互不影響,所以對(duì)于某些頻繁需要渲染的建議單獨(dú)生成一個(gè)新圖層喉酌,提高性能热凹。但也不能生成過多的圖層泵喘,會(huì)引起反作用。
通過以下幾個(gè)常用屬性可以生成新圖層
- 3D 變換:
translate3d
般妙、translateZ
will-change
-
video
纪铺、iframe
標(biāo)簽 - 通過動(dòng)畫實(shí)現(xiàn)的
opacity
動(dòng)畫轉(zhuǎn)換 position: fixed
重繪(Repaint)和回流(Reflow)
概念
重繪和回流是渲染步驟中的一小節(jié),但是這兩個(gè)步驟對(duì)于性能影響很大碟渺。
-
重繪是 當(dāng)節(jié)點(diǎn)需要更改外觀而不會(huì)影響布局的鲜锚,比如改變
color
、background-color
苫拍、visibility
等就叫稱為重繪 - 回流是 布局或者幾何屬性需要改變 就稱為回流烹棉。
注意: 回流必定會(huì)發(fā)生重繪,重繪不一定會(huì)引發(fā)回流怯疤。回流所需的成本比重繪高的多催束,改變深層次的節(jié)點(diǎn)很可能導(dǎo)致父節(jié)點(diǎn)的一系列回流集峦。
會(huì)導(dǎo)致回流的操作:
- 頁面首次渲染
- 瀏覽器窗口大小發(fā)生改變
- 元素尺寸或位置發(fā)生改變
- 元素內(nèi)容變化(文字?jǐn)?shù)量或圖片大小等等)
- 元素字體大小變化
- 添加或者刪除可見的
DOM
元素 - 激活
CSS
偽類(例如::hover
) - 查詢某些屬性或調(diào)用某些方法
一些常用且會(huì)導(dǎo)致回流的屬性和方法:
-
clientWidth
、clientHeight
抠刺、clientTop
塔淤、clientLeft
-
offsetWidth
、offsetHeight
速妖、offsetTop
高蜂、offsetLeft
-
scrollWidth
、scrollHeight
罕容、scrollTop
备恤、scrollLeft
-
scrollIntoView()
、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()
重繪和回流與Event loop關(guān)系
很多人不知道的是锦秒,重繪和回流其實(shí)和 Event loop
有關(guān)露泊。
1. 當(dāng) Event loop 執(zhí)行完 Microtasks 后,會(huì)判斷 document 是否需要更新旅择。因?yàn)闉g覽器是 60Hz 的刷新率惭笑,每 16ms 才會(huì)更新一次。
2. 然后判斷是否有 resize
或者 scroll
生真,有的話會(huì)去觸發(fā)事件沉噩,所以 resize
和 scroll
事件也是至少 16ms 才會(huì)觸發(fā)一次,并且自帶節(jié)流功能柱蟀。
3. 判斷是否觸發(fā)了 media query
4. 更新動(dòng)畫并且發(fā)送事件
5. 判斷是否有全屏操作事件
6. 執(zhí)行 requestAnimationFrame
回調(diào)
7. 執(zhí)行 IntersectionObserver
回調(diào)川蒙,該方法用于判斷元素是否可見,可以用于懶加載上长已,但是兼容性不好
8. 更新界面
9. 以上就是一幀中可能會(huì)做的事情派歌。如果在一幀中有空閑時(shí)間弯囊,就會(huì)去執(zhí)行 requestIdleCallback
回調(diào)。
減少重繪和回流
- 使用
translate
替代top
- 使用
visibility
替換display: none
胶果,因?yàn)榍罢咧粫?huì)引起重繪匾嘱,后者會(huì)引發(fā)回流(改變了布局) - 把 DOM 離線后修改,比如:先把 DOM 給
display:none
(有一次 Reflow)早抠,然后你修改 100 次霎烙,然后再把它顯示出來 - 不要把 DOM 結(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量
- 不要使用 table 布局,可能很小的一個(gè)小改動(dòng)會(huì)造成整個(gè) table 的重新布局
- 動(dòng)畫實(shí)現(xiàn)的速度的選擇蕊连,動(dòng)畫速度越快悬垃,回流次數(shù)越多,也可以選擇使用
requestAnimationFrame
- CSS 選擇符從右往左匹配查找甘苍,避免 DOM 深度過深
- 將頻繁運(yùn)行的動(dòng)畫變?yōu)閳D層尝蠕,圖層能夠阻止該節(jié)點(diǎn)回流影響別的元素。比如對(duì)于
video
標(biāo)簽载庭,瀏覽器會(huì)自動(dòng)將該節(jié)點(diǎn)變?yōu)閳D層看彼。
CSS
- 避免使用
table
布局。 - 盡可能在
DOM
樹的最末端改變class
囚聚。 - 避免設(shè)置多層內(nèi)聯(lián)樣式靖榕。
- 將動(dòng)畫效果應(yīng)用到
position
屬性為absolute
或fixed
的元素上。 - 避免使用
CSS
表達(dá)式(例如:calc()
)顽铸。
JavaScript
- 避免頻繁操作樣式茁计,最好一次性重寫
style
屬性,或者將樣式列表定義為class
并一次性更改class
屬性谓松。 - 避免頻繁操作
DOM
星压,創(chuàng)建一個(gè)documentFragment
,在它上面應(yīng)用所有DOM操作
鬼譬,最后再把它添加到文檔中租幕。 - 也可以先為元素設(shè)置
display: none
,操作結(jié)束后再把它顯示出來拧簸。因?yàn)樵?code>display屬性為none
的元素上進(jìn)行的DOM
操作不會(huì)引發(fā)回流和重繪劲绪。 - 避免頻繁讀取會(huì)引發(fā)回流/重繪的屬性,如果確實(shí)需要多次使用盆赤,就用一個(gè)變量緩存起來贾富。
- 對(duì)具有復(fù)雜動(dòng)畫的元素使用絕對(duì)定位,使它脫離文檔流牺六,否則會(huì)引起父元素及后續(xù)元素頻繁回流颤枪。