(三)渲染优化 (与浏览器为友,共进退)
渲染優(yōu)化
- 瀏覽器渲染原理和關(guān)鍵渲染路徑
- 瀏覽器的渲染流程
- 1.瀏覽器構(gòu)建對(duì)象模型(兩棵樹)
- 2.瀏覽器構(gòu)建渲染樹
- 回流與重繪, 如何避免布局抖動(dòng)
- Layout(布局)與Paint(繪制)
- 影響回流的操作
- 避免布局抖動(dòng)(layout thrashing)
- 使用FastDom【防止布局抖動(dòng)的利器】
- 使用FastDom批量對(duì)DOM的讀寫操作
- 復(fù)合線程與圖層【深入渲染流水線的最后一站】
- 復(fù)合線程(compositor thread)與圖層(layers)
- 復(fù)合圖層做什么
- 避免重繪【必學(xué),加速頁(yè)面呈現(xiàn)】
- 減少重繪的方案
- 高頻事件防抖【解救頁(yè)面卡頓的秘藥】
- 高頻 事件處理函數(shù) 防抖
- React時(shí)間調(diào)度實(shí)現(xiàn)【中高級(jí)前端需要了解的React調(diào)度原理】
- 基本原理
瀏覽器渲染原理和關(guān)鍵渲染路徑
瀏覽器是怎么把頁(yè)面渲染出來(lái)的,渲染過程分很多環(huán)節(jié),就是關(guān)鍵渲染路徑,只有理解了渲染經(jīng)歷了什么步驟,才知道針對(duì)性的進(jìn)行優(yōu)化
網(wǎng)絡(luò)資源被加載過來(lái)后,腳本、css都要進(jìn)行解析,解析完之后瀏覽器要進(jìn)行理解,如何把內(nèi)容畫到頁(yè)面上,這就是渲染的過程
主線程有Recalculate style(計(jì)算樣式)、Layout(布局)、Paint(繪制),這幾個(gè)就是渲染過程中幾個(gè)非常重要的階段
瀏覽器的渲染流程
Javascript(觸發(fā)視覺變化) 》Style(瀏覽器對(duì)樣式重新進(jìn)行計(jì)算) 》Layout(布局) 》Paint(繪制)》Composite(合成),這就是關(guān)鍵渲染路徑,一共5步,無(wú)論是首次加載還是后面頁(yè)面發(fā)生了樣式上的變化,都要經(jīng)歷這幾個(gè)步驟,最終把頁(yè)面呈現(xiàn)給用戶,理論上這5個(gè)步驟都是會(huì)被經(jīng)歷的,但是有些樣式不會(huì)影響布局,也不會(huì)影響繪制,所以瀏覽器就進(jìn)行了優(yōu)化,如果是這樣的樣式,實(shí)際上可以不經(jīng)歷布局和繪制的過程,這樣渲染就可以大大的被加速
Javascript,是可以通過Javascript實(shí)現(xiàn)一些頁(yè)面視覺上的變化,例如添加dom元素,實(shí)現(xiàn)動(dòng)畫,還可以用css做動(dòng)畫,web animation api實(shí)現(xiàn)動(dòng)畫,這些都會(huì)觸發(fā)視覺上變化
Style,瀏覽器對(duì)樣式重新進(jìn)行計(jì)算,這個(gè)過程會(huì)根據(jù)選擇器進(jìn)行重新匹配,計(jì)算哪些元素css受到影響,新的規(guī)則是什么樣的,應(yīng)該繪制成什么樣子,每個(gè)元素繪制成什么樣我們就清楚了
Layout,布局把你的元素按照你說(shuō)的樣式繪制到頁(yè)面上,要把它繪制到頁(yè)面上,這實(shí)際上是幾何問題,需要知道元素的大小、位置
Paint,繪制,真正把內(nèi)容畫到頁(yè)面上,畫文字、圖片、顏色、陰影等
Composite,合成,繪制會(huì)和這個(gè)合成聯(lián)系在一起,瀏覽器為了提高效率,并不是把所有的東西都花在同一個(gè)層里,類似ps里,會(huì)建多個(gè)圖層,最后再把它們組合起來(lái),形成我們最后的一張圖,瀏覽器為了提高效率也會(huì)把不同的東西畫在不同的層上,最終再把它們合成在一起顯示出來(lái)
當(dāng)用戶在地址欄輸入一個(gè)地址,然后回車后,到頁(yè)面顯示出來(lái)之前,都經(jīng)歷了哪些過程
當(dāng)瀏覽器拿到服務(wù)端返回的資源后,做了什么事?
無(wú)論js、css、html,都是代碼,是文本,計(jì)算機(jī)理解不了文本,所以第一步它要通過一些解釋器,把這些文本翻譯成它能理解的數(shù)據(jù)結(jié)構(gòu)
html是如何被轉(zhuǎn)換的?
首先瀏覽器下載完html文檔,就要把代碼讀進(jìn)去,讀進(jìn)去的是文本,它先把這些文本轉(zhuǎn)換成單個(gè)的字符;第二步,html里面有很多標(biāo)簽,標(biāo)簽是通過一對(duì)箭括號(hào)標(biāo)記出來(lái)的,這個(gè)箭括號(hào)就可以用作識(shí)別,就可以把一些字符串理解成有含義的標(biāo)記,這些標(biāo)記最終被換成節(jié)點(diǎn)對(duì)象,放在鏈形數(shù)據(jù)結(jié)構(gòu)里,鏈形數(shù)據(jù)結(jié)構(gòu)就類似下圖中的樹,這就可以把html描述的嵌套關(guān)系很好的表達(dá)出來(lái),通過這樣一棵樹,就可以把html的內(nèi)容、屬性、節(jié)點(diǎn)之間所有的關(guān)系都給表達(dá)清楚,這就叫DOM(文檔對(duì)象模型),描述了html的結(jié)構(gòu)
css部分如何被轉(zhuǎn)換?
一樣的道理,當(dāng)解釋器遇到你可能引用了web的css樣式表,先把資源下載過來(lái),下載完成后對(duì)這個(gè)資源進(jìn)行文本處理,把里面的標(biāo)記全部識(shí)別出來(lái),看樣式描述的是哪個(gè)節(jié)點(diǎn)的樣式,然后也用樹形結(jié)構(gòu)把這個(gè)關(guān)系存儲(chǔ)起來(lái),如下圖:除了描述節(jié)點(diǎn)之間的聯(lián)系之外,還把每個(gè)節(jié)點(diǎn)所關(guān)聯(lián)的樣式給掛載起來(lái)
1.瀏覽器構(gòu)建對(duì)象模型(兩棵樹)
構(gòu)建DOM對(duì)象:HTML>DOM
構(gòu)建CSSOM對(duì)象:CSS>CSSOM
2.瀏覽器構(gòu)建渲染樹
DOM(描述的是內(nèi)容)和CSSOM(描述的是樣式)合并成Render Tree,把內(nèi)容和樣式合在一起,讓瀏覽器理解最終我們要把什么畫在頁(yè)面上,合并的結(jié)果如下圖,把真正需要顯示的東西留下,不需要顯示的東西去掉,比如span節(jié)點(diǎn)的樣式是diaplay:none,不需要顯示在頁(yè)面上,構(gòu)造成渲染樹后,span節(jié)點(diǎn)就會(huì)被去掉,最終只會(huì)留下需要顯示到頁(yè)面上的,有了這顆渲染樹后,瀏覽器利用這棵樹,知道每個(gè)節(jié)點(diǎn)什么尺寸,畫在什么位置,
回流與重繪, 如何避免布局抖動(dòng)
Layout(布局)與Paint(繪制)
是關(guān)鍵渲染路徑中最重要的兩個(gè)步驟,也是開銷最高的兩個(gè)步驟,如何減少或避免布局和繪制的發(fā)生?
- 渲染樹只包含網(wǎng)頁(yè)需要的節(jié)點(diǎn)
- 布局根據(jù)渲染樹布局,計(jì)算每個(gè)節(jié)點(diǎn)精準(zhǔn)的位置和大小-“盒模型”,關(guān)心位置和大小
- 繪制是像素化每個(gè)節(jié)點(diǎn)的過程,把節(jié)點(diǎn)畫在屏幕上
在我們頁(yè)面中,這個(gè)關(guān)鍵路徑至少會(huì)被走完一次,也就是最開始整個(gè)頁(yè)面的加載
布局關(guān)心的是位置和大小,元素的幾何信息,所以你的樣式例如修改背景顏色不會(huì)修改位置和大小,是不會(huì)觸發(fā)布局,關(guān)鍵渲染路徑中Layout就可以跳過,直接到重繪
有沒有即不會(huì)發(fā)生布局也不會(huì)發(fā)生重繪,是有的,有些動(dòng)畫是可以利用gpu加速,這種動(dòng)畫可以直接走復(fù)合的過程,不需要進(jìn)行布局和重繪
影響回流的操作
布局也叫做回流,通常頁(yè)面第一次加載完之后,把東西放在頁(yè)面上我們叫做布局,如果是由于你之后頁(yè)面上發(fā)生了視覺上的變化又導(dǎo)致再次布局,通常叫做回流
- 添加/刪除元素
- display:none
- 移動(dòng)元素的位置
- 操作styles
- offsetLeft,scrollTop,clientWidth
- 修改瀏覽器大小,字體大小
寫個(gè)load事件,更改卡片的寬度,然后在performance里Timings那一欄load事件之后主線程有發(fā)生layout
如果一個(gè)回流操作不只影響本身,還會(huì)導(dǎo)致其他元素,甚至整個(gè)頁(yè)面所有元素的位置都發(fā)生變化,這個(gè)消耗是非常高的,甚至頁(yè)面會(huì)出現(xiàn)卡頓的狀況
避免布局抖動(dòng)(layout thrashing)
- 避免回流
比如想改變?cè)匚恢?#xff0c;千萬(wàn)不要修改top、left這樣的值,可以使用transform或者translate,通過translate做位移,這個(gè)3d動(dòng)畫既不會(huì)發(fā)生回流也不會(huì)發(fā)生重繪,只會(huì)觸發(fā)復(fù)合的過程;
減少回流:react的v-dom減少回流,把一些你要會(huì)導(dǎo)致回流發(fā)生的操作,進(jìn)行批量處理,積攢一些之后進(jìn)行統(tǒng)一的計(jì)算,最后應(yīng)用我們真正的dom上 - 讀寫分離
批量的讀操作完再進(jìn)行批量寫的操作
頁(yè)面上所有的圖片都開始進(jìn)行動(dòng)畫的變化,發(fā)現(xiàn)動(dòng)畫并不流暢
性能分析里右上角紅色三角形是表示發(fā)生了長(zhǎng)任務(wù)
提示了強(qiáng)制回流,我們應(yīng)該引起重視,這種是一個(gè)問題,問題出現(xiàn)在for循環(huán)里,給我們width進(jìn)行賦值時(shí),先取了offsetTop,瀏覽器為了提高布局的性能,會(huì)盡量把修改布局相關(guān)屬性的操作推遲,但是什么情況是無(wú)法推遲呢?當(dāng)你獲得布局相關(guān)屬性比如offsetTop時(shí)是無(wú)法推遲,不得不立即進(jìn)行最新的計(jì)算,以保證你能取得最新的結(jié)果,所以在布局前它就被強(qiáng)制進(jìn)行了一次計(jì)算,所以會(huì)先讀這個(gè)值再對(duì)width寫的操作,這是個(gè)循環(huán),有連續(xù)的讀寫,而且每次讀的步驟都會(huì)強(qiáng)制我們的布局立即進(jìn)行重新的計(jì)算,導(dǎo)致有連續(xù)不斷的回流發(fā)生,會(huì)導(dǎo)致頁(yè)面的抖動(dòng),結(jié)果頁(yè)面非常卡頓。
使用FastDom【防止布局抖動(dòng)的利器】
measure測(cè)量(讀),mutate修改(寫),把一些讀和寫的操作進(jìn)行分離,然后再通過調(diào)度函數(shù)去安排,批量進(jìn)行讀,批量進(jìn)行寫,達(dá)到消除頁(yè)面抖動(dòng)的效果
FastDom有提供相關(guān)的例子
使用Fastdom修改上面代碼
運(yùn)行后發(fā)現(xiàn)load之后沒有再出現(xiàn)右上角紅色三角形警告的長(zhǎng)任務(wù)了,也未出現(xiàn)有問題的layout
使用FastDom批量對(duì)DOM的讀寫操作
什么是FastDom
如何使用FastDom的APIS
復(fù)合線程與圖層【深入渲染流水線的最后一站】
復(fù)合線程(compositor thread)與圖層(layers)
復(fù)合主要把我們的頁(yè)面拆解成不同的圖層,當(dāng)我們的頁(yè)面發(fā)生一些視覺變化時(shí),有時(shí)這個(gè)變化可以只影響一個(gè)圖層的變化,其他圖層不需要受到影響,這樣繪制的過程可以更高效完成,
復(fù)合圖層做什么
- 將頁(yè)面拆分圖層進(jìn)行繪制再進(jìn)行復(fù)合
- 利用DevTools了解網(wǎng)頁(yè)的圖層拆分情況
頁(yè)面是怎樣拆成不同圖層的,拆的規(guī)則是什么
默認(rèn)情況下它是由瀏覽器決定的,瀏覽器會(huì)根據(jù)一些規(guī)則來(lái)判斷是否要將頁(yè)面去拆分成多個(gè)圖層,又把哪些元素拆分成一個(gè)單獨(dú)的圖層,主要分析的是元素和元素之間是否有相互的影響,如果某些元素對(duì)其他元素造成的影響非常多,它就會(huì)被提取成一個(gè)單獨(dú)的圖層,這樣的好處是如果它發(fā)生變化,只對(duì)它這個(gè)圖層進(jìn)行相關(guān)的重繪,而不會(huì)影響其他部分
我們也可以主動(dòng)的把一些元素提取成一個(gè)單獨(dú)的圖層,我們知道這些元素會(huì)影響其他部分,我們把它提取出來(lái),讓它的變化變得更獨(dú)立
左下角顯示有兩個(gè)圖層,點(diǎn)擊頁(yè)面會(huì)顯示出范圍
- 哪些樣式僅影響復(fù)合,不觸發(fā)布局和重繪
當(dāng)我們使用這些屬性時(shí),如果可以所涉及到的元素提取到單獨(dú)的圖層,這些元素在發(fā)生動(dòng)畫或者視覺上變化時(shí),就只會(huì)觸發(fā)復(fù)合,不會(huì)觸發(fā)布局和重繪
如下圖,把卡片動(dòng)畫都拆成單獨(dú)的圖層,讓gpu進(jìn)行單獨(dú)的處理
performance錄制,移到卡片上的效果動(dòng)畫,如下圖,發(fā)現(xiàn)會(huì)對(duì)樣式進(jìn)行重新計(jì)算,更新圖層樹,并沒有發(fā)生布局和重繪,直接觸發(fā)復(fù)合
但是如果把所有東西都拆到單獨(dú)的圖層中,也不行,圖層越多,開銷越高,會(huì)適得其反,會(huì)使頁(yè)面所有操作非常慢,所以只把特定的,我們真正需要的,能達(dá)到這種效果的元素才提取到單獨(dú)的圖層
避免重繪【必學(xué),加速頁(yè)面呈現(xiàn)】
錄制動(dòng)畫進(jìn)行分析,4s左右點(diǎn)擊觸發(fā)了動(dòng)畫,觸發(fā)動(dòng)畫后主線程開始繁忙,放大仔細(xì)看,有很多組,因?yàn)閯?dòng)畫是持續(xù)不斷的,我們可以看其中一組,首先會(huì)進(jìn)行重新計(jì)算樣式、更新圖層樹、再進(jìn)行復(fù)合,所有任務(wù)執(zhí)行得很快,沒有長(zhǎng)任務(wù),也沒有導(dǎo)致強(qiáng)制布局和布局抖動(dòng)的問題
還有種方式看頁(yè)面有沒有發(fā)生重繪,修改樣式如下,預(yù)計(jì)會(huì)發(fā)生布局和重繪
command+shift+p,勾選上Paint flashing,如果頁(yè)面發(fā)生重繪,所重繪的區(qū)域會(huì)用綠色標(biāo)記出來(lái),非常方便我們觀察頁(yè)面有沒有發(fā)生重繪
雖然tranform和opacity只影響復(fù)合,但是不要忘記做一件事,把它所影響到的元素提取到一個(gè)單獨(dú)的圖層,那是怎么做的?
在卡片的root樣式類里有做個(gè)聲明,利用了willChange屬性,willChange值設(shè)置為transform,willChange: ‘transform’,這樣瀏覽器就知道這個(gè)元素應(yīng)當(dāng)被提取到一個(gè)單獨(dú)的圖層里去進(jìn)行,
減少重繪的方案
利用Devtools識(shí)別paint的瓶頸
利用will-change創(chuàng)建新的圖層
高頻事件防抖【解救頁(yè)面卡頓的秘藥】
高頻 事件處理函數(shù) 防抖
高頻 事件處理函數(shù):有一些事件觸發(fā)頻率非常高,甚至?xí)^幀的刷新速率,比如滾動(dòng)scroll、touchstart、touchmove、鼠標(biāo)一類的mousemove等,這些函數(shù)觸發(fā)頻率非常快,快到肉眼看不出來(lái),可以通過性能測(cè)量工具試試,把幀那一行和main函數(shù)里關(guān)于事件觸發(fā)的這些任務(wù)對(duì)齊看下,很容易發(fā)現(xiàn)這類事件在一幀里會(huì)觸發(fā)多次,導(dǎo)致在一幀里對(duì)這些事件要多次響應(yīng),如果事件處理函數(shù)里消耗比較高,那在一幀里任務(wù)會(huì)比較重,但是實(shí)際上并沒有必要在一幀里處理多次,比如滾動(dòng),并不關(guān)心中間的過程,只關(guān)心最后滾動(dòng)到哪里,而之前多出來(lái)的幾次滾動(dòng)造成任務(wù)量比較重,沒有辦法保證一幀能在16ms內(nèi)完成,頁(yè)面就會(huì)出現(xiàn)卡頓,也就是抖動(dòng)
下面造一個(gè)pointer函數(shù)復(fù)現(xiàn)抖動(dòng)問題
下面先看下一幀的生命周期,看下一幀是怎么被觸發(fā)的
首先事件觸發(fā),然后js觸發(fā)視覺上的變化,一幀開始,rAF調(diào)用,layout布局,paint重繪
rAF是在布局和重繪之前調(diào)用,這樣可以利用rAF先把我們要做的處理先做完之后再去進(jìn)行布局和繪制,極大的提高效率,另外rAF本身是由javascript進(jìn)行調(diào)度的,會(huì)盡量讓你能夠在每一次繪制之前去觸發(fā)這個(gè)rAF,盡量達(dá)到60fps的效果
rAF使用及防抖實(shí)現(xiàn)如下:
React時(shí)間調(diào)度實(shí)現(xiàn)【中高級(jí)前端需要了解的React調(diào)度原理】
基本原理
- requestIdleCallback的問題
想模擬requestIdleCallback,requestIdleCallback是官方給出的標(biāo)準(zhǔn),是另外一個(gè)函數(shù),它的執(zhí)行是希望在一幀16ms的時(shí)間內(nèi),如果還有空余的時(shí)間,可以讓它做些事情,但是這個(gè)函數(shù)并沒有被瀏覽器進(jìn)行很好的支持,所以兼容性不好,react框架考慮到這點(diǎn),并沒有直接采用這個(gè)函數(shù),而是通過rAF模擬實(shí)現(xiàn)rIC - 通過rAF模擬rIC
requestIdleCallback做什么,在什么時(shí)候做?
上圖中很清晰的描述了一幀這樣一個(gè)關(guān)鍵渲染周期內(nèi)都做了什么事
requestAnimationFrame是在layout和paint之前被觸發(fā),這一幀要開始渲染之前
requestIdleCallback是在layout和paint之后被觸發(fā),這一幀已畫完了,還有剩余的時(shí)間,可以做些額外的事情,但是這個(gè)事要有個(gè)度,因?yàn)橐o主線程留更多的空余時(shí)間,要留出時(shí)間處理用戶的交互,沒辦法預(yù)期什么時(shí)候用戶會(huì)和你的頁(yè)面進(jìn)行交互,所以要盡量多的留出空閑時(shí)間,因?yàn)橐坏┯薪换ミ^來(lái),我們至少要留50ms給每一次交互去做處理,所以react做這個(gè)時(shí)也會(huì)考慮這點(diǎn),不會(huì)把所有的地方全占滿,根據(jù)requestAnimationFrame可以算出requestIdleCallback的時(shí)間,requestAnimationFrame是在一幀開始的時(shí)候,這一幀也有上限時(shí)間,根據(jù)這兩個(gè)時(shí)間就可以計(jì)算出requestIdleCallback的時(shí)間
在用戶不再看這個(gè)頁(yè)面,或者說(shuō)現(xiàn)在頁(yè)面不可見,屬于后臺(tái)狀態(tài)時(shí),requestAnimationFrame實(shí)際上不會(huì)運(yùn)行,react只是借用這個(gè)函數(shù),需要讓任務(wù)即使是在后臺(tái)狀態(tài)時(shí)還要繼續(xù)完成,所以需要找個(gè)替代方案,能保證在后臺(tái)繼續(xù)把任務(wù)做完,所以如下圖用setTimeout做一個(gè)替代方案實(shí)現(xiàn)
作為調(diào)度函數(shù),最關(guān)心的是所有任務(wù),會(huì)給這些任務(wù)安排優(yōu)先級(jí),react這邊安排了5個(gè)優(yōu)先級(jí),從立即可以執(zhí)行到有空閑執(zhí)行的,另外這些任務(wù)都有個(gè)過期時(shí)間,還有就是這些任務(wù)的存儲(chǔ),肯定有一個(gè)隊(duì)列,把這些任務(wù)排到隊(duì)列里,然后等待IdleCallback,有空閑時(shí)間時(shí)去執(zhí)行,底層的實(shí)現(xiàn)是一個(gè)雙向的環(huán)形鏈表,如何利用環(huán)形鏈表去安排整個(gè)任務(wù)的優(yōu)先級(jí)
總結(jié)
以上是生活随笔為你收集整理的(三)渲染优化 (与浏览器为友,共进退)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信更新到最新版免费领取QQ音乐VIP体
- 下一篇: Exchange混合部署环境下如何手工创