【W(wǎng)eb技術(shù)】1151- 重排、重繪、合成,你真的懂嗎?

前言
重排、重繪、合成是什么?總的來說,它們都是瀏覽器渲染頁面進(jìn)程中的一個(gè)小小的環(huán)節(jié),正是這些小環(huán)節(jié)按照一定的規(guī)則有條不紊的運(yùn)作,才保證了我們能正常、順暢地網(wǎng)上沖浪。 本文主要介紹了重排、重繪、合成的基本概念、觸發(fā)時(shí)機(jī)、影響范圍以及其優(yōu)化策略。 我們首先來看一下,瀏覽器的渲染原理,看一下,它們分別擔(dān)任了什么工作?
一、瀏覽器渲染原理
結(jié)合上圖,一個(gè)完整的渲染流程大致可總結(jié)為如下幾個(gè)步驟:
HTML被HTML解析器解析成DOM Tree CSS則被CSS解析器解析成CSSOM Tree DOM Tree和CSSOM Tree解析完成后,被附加到一起,形成渲染樹(Render Tree) 布局,根據(jù)渲染樹計(jì)算每個(gè)節(jié)點(diǎn)的幾何信息生成布局樹(Layout Tree) 對(duì)布局樹進(jìn)行分層,并生成分層樹(Layer Tree) 為每個(gè)圖層生成繪制列表 渲染繪制(Paint)。根據(jù)計(jì)算好的繪制列表信息繪制整個(gè)頁面,并將其提交到合成線程 合成線程將圖層分成圖塊,并在光柵化線程池中將圖塊轉(zhuǎn)換成位圖,發(fā)送繪制圖塊命令 DrawQuad 給瀏覽器進(jìn)程 瀏覽器進(jìn)程根據(jù) DrawQuad 消息生成頁面,并顯示到顯示器上
二、重排
定義:當(dāng)通過JS或css改變了元素的寬度、高度等,修改了元素的幾何位置屬性,那么瀏覽器會(huì)觸發(fā)重新布局,解析之后的一系列子階段,這個(gè)過程就叫重排。無疑,重排需要更新完整的渲染流水線,所以開銷也是最大的。
觸發(fā)時(shí)機(jī)和影響范圍:DOM節(jié)點(diǎn)信息更改,觸發(fā)重排時(shí),這個(gè)DOM更改程度會(huì)決定周邊DOM更改范圍。
全局范圍:就是從根節(jié)點(diǎn)html開始對(duì)整個(gè)渲染樹進(jìn)行重新布局,例如當(dāng)我們改變了窗口尺寸或方向或者是修改了根元素的尺寸或者字體大小等。 局部范圍:對(duì)渲染樹的某部分或某一個(gè)渲染對(duì)象進(jìn)行重新布局。
三、重繪
定義:如果修改了元素的背景顏色,并沒有引起幾何位置的變換,所以就直接進(jìn)入了繪制階段,然后執(zhí)行之后的一系列子階段,這個(gè)過程就叫重繪。相較于重排操作,重繪省去了布局和分層階段,所以執(zhí)行效率會(huì)比重排操作要高一些。
觸發(fā)時(shí)機(jī)和影響范圍:每一次的dom更改或者css幾何屬性更改,都會(huì)引起一次瀏覽器的重排/重繪過程,而如果是css的非幾何屬性更改,則只會(huì)引起重繪過程。所以說重排一定會(huì)引起重繪,而重繪不一定會(huì)引起重排,重繪的開銷較小,重排的代價(jià)較高。
四、合成
定義:合成是一種將頁面的各個(gè)部分分離成層(Layer Tree),分別將它們柵格化,然后在稱為“合成線程”的中組合為頁面的技術(shù)。
觸發(fā)時(shí)機(jī)和影響范圍:在GUI渲染線程后執(zhí)行,將GUI渲染線程生成的繪制列表轉(zhuǎn)換為位圖,然后發(fā)送繪制圖塊命令 DrawQuad 給瀏覽器進(jìn)程,瀏覽器進(jìn)程根據(jù) DrawQuad 消息生成頁面,將頁面顯示到顯示器上
優(yōu)點(diǎn):我們使用了 CSS 的 transform 來實(shí)現(xiàn)動(dòng)畫效果,避開了重排和重繪階段,直接在非主線程上執(zhí)行合成動(dòng)畫操作。這樣的效率是最高的,因?yàn)槭窃诜侵骶€程上合成,并沒有占用主線程的資源,另外也避開了布局和繪制兩個(gè)子階段,所以相對(duì)于重繪和重排,合成能大大提升繪制效率。
五、常見的觸發(fā)重排、重繪的屬性和方法
常見的觸發(fā)重排、重繪的屬性和方法

全局范圍重排、局部范圍重排、重繪的影響范圍示例

六、優(yōu)化策略
6.1 減少DOM操作
最小化DOM訪問次數(shù),盡量緩存訪問DOM的樣式信息,避免過度觸發(fā)重排。 如果在一個(gè)局部方法中需要多次訪問同一個(gè)dom,可以在第一次獲取元素時(shí)用變量保存下來,減少遍歷時(shí)間。 用事件委托來減少事件處理器的數(shù)量。 用querySelectorAll()替代getElementByXX()。
querySelectorAll():獲取靜態(tài)集合,通過函數(shù)獲取元素之后,元素之后的改變并不會(huì)影響之前獲取后存儲(chǔ)到的變量。也就是獲取到元素之后就和html中的這個(gè)元素沒有關(guān)系了 getElementByXX():獲取動(dòng)態(tài)集合,通過函數(shù)獲取元素之后,元素之后的改變還是會(huì)動(dòng)態(tài)添加到已經(jīng)獲取的這個(gè)元素中。換句話說,通過這個(gè)方法獲取到元素存儲(chǔ)到變量的時(shí)候,以后每一次在Javascript函數(shù)中使用這個(gè)變量的時(shí)候都會(huì)再去訪問一下這個(gè)變量對(duì)應(yīng)的html元素。
6.2 減少重排
放棄傳統(tǒng)操作DOM的時(shí)代,基于vue/react開始數(shù)據(jù)影響視圖模式。 避免設(shè)置大量的style內(nèi)聯(lián)屬性,因?yàn)橥ㄟ^設(shè)置style屬性改變結(jié)點(diǎn)樣式的話,每一次設(shè)置都會(huì)觸發(fā)一次reflow,所以最好是使用class屬性。 不要使用table布局,因?yàn)閠able中某個(gè)元素一旦觸發(fā)了reflow,那么整個(gè)table的元素都會(huì)觸發(fā)reflow。那么在不得已使用table的場(chǎng)合,可以設(shè)置table-layout:auto;或者是table-layout:fixed這樣可以讓table一行一行的渲染,這種做法也是為了限制reflow的影響范圍。 盡量少使用display:none可以使用visibility:hidden代替,display:none會(huì)造成重排,visibility:hidden只會(huì)造成重繪。 使用resize事件時(shí),做防抖和節(jié)流處理。 分離讀寫操作(現(xiàn)代的瀏覽器都有渲染隊(duì)列的機(jī)制)
分離讀寫減少重排的原理
