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