日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

(三)渲染优化 (与浏览器为友,共进退)

發布時間:2023/12/31 HTML 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (三)渲染优化 (与浏览器为友,共进退) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

渲染優化

  • 瀏覽器渲染原理和關鍵渲染路徑
    • 瀏覽器的渲染流程
      • 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創建新的圖層

import React from 'react'; import MaterialUICard from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; import Button from '@material-ui/core/Button'; import Typography from '@material-ui/core/Typography'; import {withStyles} from '@material-ui/core/styles'; import './animation.css'; import {LazyLoadImage} from 'react-lazy-load-image-component'; import 'react-lazy-load-image-component/src/effects/blur.css';const styles = theme => ({root: {margin: theme.spacing(1),willChange: 'transform'},card: {width: 300},cardSpinning: {width: 300,animation: '3s linear 1s infinite running rotate'},media: {height: 200,width: 300,objectFit: 'cover',} });class MyCard extends React.Component {constructor(props) {super(props);this.state = {spinning: false,}}spin = () => {this.setState({spinning: true});}render() {/*根據Spinning進行判斷決定是否旋轉*/let cardClass = this.state.spinning ? this.props.classes.cardSpinning : this.props.classes.card;return (<div className={this.props.classes.root}>{/*添加點擊事件,觸發動畫*/}<MaterialUICard className={cardClass} onClick={this.spin}><LazyLoadImageclassName={this.props.classes.media}src={this.props.image}effect="blur"rel="preconnect"/><CardContent><Typography gutterBottom variant="h6" component="h2">{this.props.title}</Typography><Typography component="p">{this.props.description}</Typography></CardContent><CardActions className={this.props.classes.actions}><Button size="small" color="primary" variant="outlined">聯系方式:🐟🐡🐭🦆 Dereck</Button></CardActions></MaterialUICard></div>);} }export default withStyles(styles)(MyCard);

高頻事件防抖【解救頁面卡頓的秘藥】

高頻 事件處理函數 防抖

高頻 事件處理函數:有一些事件觸發頻率非常高,甚至會超過幀的刷新速率,比如滾動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,有空閑時間時去執行,底層的實現是一個雙向的環形鏈表,如何利用環形鏈表去安排整個任務的優先級

總結

以上是生活随笔為你收集整理的(三)渲染优化 (与浏览器为友,共进退)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。