[1.1W字]寫給女朋友的祕籍-瀏覽器工做原理(渲染流程)篇

前言

想要成爲一名合格的前端工程師,掌握相關瀏覽器的工做原理是必備的,這樣子纔會有一個完整知識體系,要是能參透瀏覽器的工做原理,你就能解決80%的前端難題css

其餘文章:html

這篇文章準備梳理一下渲染流程,也就是瀏覽器是怎麼把HTML,CSS,JS,圖片等資源最後顯示漂亮的頁面。前端

仍是那句話,瞭解瀏覽器是如何工做的,能讓你站在更高維度去理解前端html5

但願經過這篇文章,可以讓你從新認識瀏覽器,並把JavaScript,網絡,頁面渲染,瀏覽器安全等知識串聯起來,從而讓你對整個前端體系有全新的認識。web

這篇主要是梳理了渲染流程中幾個重要的步驟,以及從中有哪些優化的點,怎麼樣避免和減小重繪、重排,對優化性能上有必定的幫助。面試

讀完這一期內容,你將收穫算法

  • 前端性能優化的底層邏輯;
  • 瀏覽器頁面渲染的核心流程
  • JavaScript 運行機制解析
  • 瀏覽器網絡及安全機制解析

小聲說:歡迎在留言區與我分享你的想法,也歡迎你在留言區記錄你的思考過程👊chrome


若是喜歡的話能夠點贊/關注,支持一下,但願你們能夠看完本文有所收穫

回顧上一期

講瀏覽器中渲染流程,先把上一篇所梳理的內容大概回顧一下segmentfault

上一篇文章:[1.2W字👍]寫給女朋友的祕籍-瀏覽器工做原理(上)篇api

瀏覽器架構(Chrome爲主)

主要梳理如下知識點:

  • 梳理單線程與多線程概念,進程與線程區別
  • 以Chrome瀏覽器爲主,梳理了Chrome瀏覽器發展史,從單進程瀏覽器到多進程瀏覽器
  • 單進程架構到多進程架構趨勢,發展趨勢的優缺點
  • 如今多數瀏覽器多進程架構:主進程 渲染進程 插件進程 GPU進程 網絡進程

Http請求

主要梳理如下知識點:

  • Http請求的總體流程,大體分爲八個階段

  • 八個階段歸納爲: 構建請求 查找緩存 準備 IP 和端口 等待 TCP 隊列 創建 TCP 鏈接 發起 HTTP 請求 服務器處理請求 服務器返回請求和斷開鏈接

  • 每一個階段大體的工做原理梳理了一下

  • 瀏覽器緩存順帶提起了一下(面試常考) 性能優化一部分

導航流程:輸入url到頁面展現

主要梳理知識點:

  • 總體流程,梳理這個過程當中最爲主要的三個進程,瀏覽器進程 網絡進程 渲染進程,它們各自的職責以及三者之間的通訊
  • 嘗試去分析每一個階段,細節不說起了。

渲染進程接受到CommitNavigation消息以後,便開始與網絡進程創建數據管道提及,此時渲染進程開始幹活了

那麼纔有了咱們接下來要梳理的內容,渲染進程如何工做的呢

渲染流程

首先要了解的概念:

  • 渲染引擎:它是瀏覽器最核心的部分是 「Rendering Engine」,不過咱們通常習慣將之稱爲 「瀏覽器內核」

  • 渲染引擎主要包括的線程:

  • 各個線程主要職責

    • GUI渲染線程:GUI 渲染線程負責渲染瀏覽器界面,解析 HTML,CSS,構建 DOM 樹和 RenderObject 樹,佈局和繪製等。當界面須要重繪(Repaint)或因爲某種操做引起迴流(Reflow)時,該線程就會執行。
    • JavaScript引擎線程: JavaScript 引擎線程主要負責解析 JavaScript 腳本並運行相關代碼。 JavaScript 引擎在一個Tab頁(Renderer 進程)中不管何時都只有一個 JavaScript 線程在運行 JavaScript 程序。須要提起一點就是,GUI線程與JavaScript引擎線程是互斥的,這也是就是爲何JavaScript操做時間過長,會形成頁面渲染不連貫,致使頁面出現阻塞的原理。
    • 事件觸發線程:當一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待 JavaScript 引擎的處理。 一般JavaScript引擎是單線程的,因此這些事件都會排隊等待JS執行。
    • 定時器觸發器: 咱們平常使用的setInterval 和 setTimeout 就在該線程中,緣由可能就是:因爲JS引擎是單線程的,若是處於阻塞線程狀態就會影響記時的準確,因此須要經過單獨的線程來記時並觸發響應的事件這樣子更爲合理。
    • Http請求線程: 在 XMLHttpRequest 在鏈接後是經過瀏覽器新開一個線程請求,這個線程就Http請求線程,它 將檢測到狀態變動時,若是設置有回調函數,異步線程就產生狀態變動事件放到 JavaScript 引擎的處理隊列中等待處理。

以上來自阿寶哥總結:你不知道的 Web Workers (上)[7.8K 字 | 多圖預警]

有了上述的概念,對接下咱們講渲染流水線會有所幫助

簡略版的渲染機制

好久以前就把瀏覽器工做原理讀完了,看了不少博客,文章,當時簡簡單單的梳理一些內容,以下👇

簡略版渲染機制通常分爲如下幾個步驟

  1. 處理 HTML 並構建 DOM 樹。
  2. 處理 CSS 構建 CSSOM 樹。
  3. 將 DOM 與 CSSOM 合併成一個渲染樹。
  4. 根據渲染樹來佈局,計算每一個節點的位置。
  5. 調用 GPU 繪製,合成圖層,顯示在屏幕上。

接下來大概就是這麼說:

在構建 CSSOM 樹時,會阻塞渲染,直至 CSSOM 樹構建完成。而且構建 CSSOM 樹是一個十分消耗性能的過程,因此應該儘可能保證層級扁平,減小過分層疊,越是具體的 CSS 選擇器,執行速度越慢。

當 HTML 解析到 script 標籤時,會暫停構建 DOM,完成後纔會從暫停的地方從新開始。也就是說,若是你想首屏渲染的越快,就越不該該在首屏就加載 JS 文件。而且 CSS 也會影響 JS 的執行,只有當解析完樣式表纔會執行 JS,因此也能夠認爲這種狀況下,CSS 也會暫停構建 DOM。

說完這些,記下來就講幾個面試經常會提起的,會問你的知識點👇

Load 和 DOMContentLoaded 區別

Load 事件觸發表明頁面中的 DOM,CSS,JS,圖片已經所有加載完畢。

DOMContentLoaded 事件觸發表明初始的 HTML 被徹底加載和解析,不須要等待 CSS,JS,圖片加載。

圖層

通常來講,能夠把普通文檔流當作一個圖層。特定的屬性能夠生成一個新的圖層。不一樣的圖層渲染互不影響,因此對於某些頻繁須要渲染的建議單獨生成一個新圖層,提升性能。但也不能生成過多的圖層,會引發副作用。

經過如下幾個經常使用屬性能夠生成新圖層

  • 3D 變換:translate3dtranslateZ
  • will-change
  • videoiframe 標籤
  • 經過動畫實現的 opacity 動畫轉換
  • position: fixed

重繪(Repaint)和迴流(Reflow)

重繪和迴流是渲染步驟中的一小節,可是這兩個步驟對於性能影響很大。

  • 重繪是當節點須要更改外觀而不會影響佈局的,好比改變 color 就叫稱爲重繪
  • 迴流是佈局或者幾何屬性須要改變就稱爲迴流。

迴流一定會發生重繪,重繪不必定會引起迴流。迴流所需的成本比重繪高的多,改變深層次的節點極可能致使父節點的一系列迴流。

因此如下幾個動做可能會致使性能問題:

  • 改變 window 大小
  • 改變字體
  • 添加或刪除樣式
  • 文字改變
  • 定位或者浮動
  • 盒模型

不少人不知道的是,重繪和迴流其實和 Event loop 有關。

  1. 當 Event loop 執行完 Microtasks 後,會判斷 document 是否須要更新。由於瀏覽器是 60Hz 的刷新率,每 16ms 纔會更新一次。
  2. 而後判斷是否有 resize 或者 scroll ,有的話會去觸發事件,因此 resizescroll 事件也是至少 16ms 纔會觸發一次,而且自帶節流功能。
  3. 判斷是否觸發了 media query
  4. 更新動畫而且發送事件
  5. 判斷是否有全屏操做事件
  6. 執行 requestAnimationFrame 回調
  7. 執行 IntersectionObserver 回調,該方法用於判斷元素是否可見,能夠用於懶加載上,可是兼容性很差
  8. 更新界面
  9. 以上就是一幀中可能會作的事情。若是在一幀中有空閒時間,就會去執行 requestIdleCallback 回調。

以上內容來自於 HTML 文檔

減小重繪和迴流

  • 使用 translate 替代 top

    <div class="test"></div>
    <style>
    	.test {
    		position: absolute;
    		top: 10px;
    		width: 100px;
    		height: 100px;
    		background: red;
    	}
    </style>
    <script>
    	setTimeout(() => {
            // 引發迴流
    		document.querySelector('.test').style.top = '100px'
    	}, 1000)
    </script>
    複製代碼
  • 使用 visibility 替換 display: none ,由於前者只會引發重繪,後者會引起迴流(改變了佈局)

  • 把 DOM 離線後修改,好比:先把 DOM 給 display:none (有一次 Reflow),而後你修改100次,而後再把它顯示出來

  • 不要把 DOM 結點的屬性值放在一個循環裏當成循環裏的變量

    for(let i = 0; i < 1000; i++) {
        // 獲取 offsetTop 會致使迴流,由於須要去獲取正確的值
        console.log(document.querySelector('.test').style.offsetTop)
    }
    複製代碼
  • 不要使用 table 佈局,可能很小的一個小改動會形成整個 table 的從新佈局

  • 動畫實現的速度的選擇,動畫速度越快,迴流次數越多,也能夠選擇使用 requestAnimationFrame

  • CSS 選擇符從右往左匹配查找,避免 DOM 深度過深

  • 將頻繁運行的動畫變爲圖層,圖層可以阻止該節點回流影響別的元素。好比對於 video 標籤,瀏覽器會自動將該節點變爲圖層。


這不是我想梳理的內容

上面的渲染流程是我一年前就在我筆記中存在的內容,我還記得當時學習的方式是囫圇吞棗式的,上面☝簡略版的渲染流程,我印象中是在GitHub上面某博看看的,當時直接Copy下來的,當時以爲這個渲染原理這塊有了別人梳理好的結論,本身多看看,會記住的,事實上,面試的時候,提起這部分的時候,深度明顯不夠,天然就被問倒了

下來梳理了一份詳細的版本,坦白說,做爲一個學者,天然是站在巨人的肩膀上,去總結梳理知識,我認爲這是對我最有效的學習方式

讓我帶着你🚗重溫通常渲染流程吧


詳細版的渲染機制

較爲專業的術語總結爲如下階段:

  1. 構建DOM樹
  2. 樣式計算
  3. 佈局階段
  4. 分層
  5. 繪製
  6. 分塊
  7. 光柵化
  8. 合成

你能夠想象一下,從0,1字節流到最後頁面展示在你面前,這裏面渲染機制確定很複雜,因此渲染模塊把執行過程當中化爲不少的子階段,渲染引擎從網絡進程拿到字節流數據後,通過這些子階段的處理,最後輸出像素,這個過程能夠稱爲渲染流水線 ,咱們從一張圖上來看👇

那接下來就從每一個階段來梳理一下大體過程。

構建DOM樹

這個過程主要工做就是講HTML內容轉換爲瀏覽器DOM樹結構

  • 字節→字符→令牌→節點→對象模型(DOM)

文檔對象模型(DOM)

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>
複製代碼

咱們先看看數據是怎麼樣轉換的👇

大概過程:

  1. **轉換:**瀏覽器從磁盤或網絡讀取 HTML 的原始字節,並根據文件的指定編碼(例如 UTF-8)將它們轉換成各個字符。
  2. **令牌化:**瀏覽器將字符串轉換成 W3C HTML5 標準規定的各類令牌,例如,「」、「」,以及其餘尖括號內的字符串。每一個令牌都具備特殊含義和一組規則。
  3. **詞法分析:**發出的令牌轉換成定義其屬性和規則的「對象」。
  4. **DOM構建:**最後,因爲 HTML 標記定義不一樣標記之間的關係(一些標記包含在其餘標記內),建立的對象連接在一個樹數據結構內,此結構也會捕獲原始標記中定義的父項-子項關係:HTML 對象是 body 對象的父項,bodyparagraph 對象的父項,依此類推。

咱們把上述這樣子的過程就叫作是構建DOM樹過程

樣式計算

這個子階段主要有三個步驟

  • 格式化樣式表
  • 標準化樣式表
  • 計算每一個DOM節點具體樣式

格式化樣式表

咱們拿到的也就是0,1字節流數據,瀏覽器沒法直接去識別的,因此渲染引擎收到CSS文本數據後,會執行一個操做,轉換爲瀏覽器能夠理解的結構-styleSheets

若是你很想了解這個格式化的過程,能夠好好去研究下,不一樣的瀏覽器可能在CSS格式化過程當中會有所不一樣,在這裏就不展開篇幅了。

經過瀏覽器的控制檯document.styleSheets能夠來查看這個最終結果。經過JavaScript能夠完成查詢和修改功能,或者說這個階段爲後面的樣式操做提供基石。

標準化樣式表

什麼是標準化樣式表呢?先看一段CSS文本👇

body { font-size: 2em }
p {color:blue;}
span  {display: none}
div {font-weight: bold}
div  p {color:green;}
div {color:red; }
複製代碼

有些時候,咱們寫CSS 樣式的時候,會寫font-size:2em;color:red;font-weight:bold,像這些數值並不容易被渲染引擎所理解,所以須要在計算樣式以前將它們標準化,如em->px,red->rgba(255,0,0,0),bold->700等等。

上面的代碼標準後屬性值是什麼樣子呢👇

計算每一個DOM節點具體樣式

經過格式化標準化,接下來就是計算每一個節點具體樣式信息了。

計算規則:繼承層疊

繼承:每一個子節點會默認去繼承父節點的樣式,若是父節點中找不到,就會採用瀏覽器默認的樣式,也叫UserAgent樣式

層疊:樣式層疊,是CSS一個基本特徵,它定義如何合併來自多個源的屬性值的算法。某種意義上,它處於核心地位,具體的層疊規則屬於深刻 CSS 語言的範疇,這裏就補展開篇幅說了。

不過值得注意的是,在計算完樣式以後,全部的樣式值會被掛在到window.getComputedStyle當中,也就是能夠經過JS來獲取計算後的樣式,很是方便。

這個階段,完成了DOM節點中每一個元素的具體樣式,計算過程當中要遵循CSS的繼承層疊兩條規則,最終輸出的內容是每一個節點DOM的樣式,被保存在ComputedStyle中。

想了解每一個 DOM 元素最終的計算樣式,能夠打開 Chrome 的「開發者工具」,選擇第一個「element」標籤,好比我下面就選擇了div標籤,而後再選擇「Computed」子標籤,以下圖所示:

另一種說法CSSOM

若是不是很理解的話,能夠看這裏👇

跟處理HTML同樣,咱們須要更具CSS兩個規則:繼承層疊轉換成某種瀏覽器能理解和處理的東西,處理過程相似處理HTML,如上圖☝

CSS 字節轉換成字符,接着轉換成令牌和節點,最後連接到一個稱爲「CSS 對象模型」(CSSOM) 的樹結構內👇

不少人確定看這個很熟悉,確實,不少博客都是基於CSSOM說法來說的,我要說的是:

和DOM不同,源碼中並無CSSOM這個詞,因此不少文章說的CSSOM應該就是styleSheets,固然了這個styleSheets咱們能夠打印出來的

不少文章說法是渲染樹也是16年前的說法,如今代碼重構了,咱們能夠把LayoutTree當作是渲染樹,不過它們之間仍是有些區別的。

生成佈局樹

上述過程已經完成DOM樹(DOM樹)構建,以及樣式計算(DOM樣式),接下來就是要經過瀏覽器的佈局系統肯定元素位置,也就是生成一顆佈局樹(Layout Tree),以前說法叫 渲染樹

建立佈局樹

  1. 在DOM樹上不可見的元素,head元素,meta元素等,以及使用display:none屬性的元素,最後都不會出如今佈局樹上,因此瀏覽器佈局系統須要額外去構建一棵只包含可見元素佈局樹。

  2. 咱們直接結合圖來看看這個佈局樹構建過程:

    爲了構建佈局樹,瀏覽器佈局系統大致上完成了下面這些工做:

  • 遍歷DOM樹可見節點,並把這些節點加到佈局樹中
  • 對於不可見的節點,head,meta標籤等都會被忽略。對於body.p.span 這個元素,它的屬性包含display:none,因此這個元素沒有被包含進佈局樹。

佈局計算

接下來就是要計算佈局樹節點的座標位置,佈局的計算過程很是複雜,張開介紹的話,會顯得文章過於臃腫,大多數狀況下,咱們只須要知道它所作的工做是什麼,想知道它是如何作的話,能夠看看如下兩篇文章👇

梳理前三個階段

一圖歸納上面三個階段

分層

  • 生成圖層樹(Layer Tree)
  • 擁有層疊上下文屬性的元素會被提高爲單獨一層
  • 須要裁剪(clip)的地方也會建立圖層
  • 圖層繪製

首先須要知道的就是,瀏覽器在構建完佈局樹後,還須要進行一系列操做,這樣子可能考慮到一些複雜的場景,好比一些些複雜的 3D 變換、頁面滾動,或者使用 z-indexing 作 z 軸排序等,還有好比是含有層疊上下文如何控制顯示和隱藏等狀況。

生成圖層樹

你最終看到的頁面,就是由這些圖層一塊兒疊加構成的,它們按照必定的順序疊加在一塊兒,就造成了最終的頁面。

瀏覽器的頁面實際上被分紅了不少圖層,這些圖層疊加後合成了最終的頁面。

咱們來看看圖層與佈局樹之間關係,以下圖👇

一般狀況下,並非佈局樹的每一個節點都包含一個圖層,若是一個節點沒有對應的層,那麼這個節點就從屬於父節點的圖層。

那什麼狀況下,渲染引擎會爲特定的節點建立新圖層呢?

有兩種狀況須要分別討論,一種是顯式合成,一種是隱式合成

顯式合成

1、 擁有層疊上下文的節點。

層疊上下文也基本上是有一些特定的CSS屬性建立的,通常有如下狀況:

  1. HTML根元素自己就具備層疊上下文。
  2. 普通元素設置position不爲static而且設置了z-index屬性,會產生層疊上下文。
  3. 元素的 opacity 值不是 1
  4. 元素的 transform 值不是 none
  5. 元素的 filter 值不是 none
  6. 元素的 isolation 值是isolate
  7. will-change指定的屬性值爲上面任意一個。(will-change的做用後面會詳細介紹)

2、須要剪裁(clip)的地方。

好比一個標籤很小,50*50像素,你在裏面放了很是多的文字,那麼超出的文字部分就須要被剪裁。固然若是出現了滾動條,那麼滾動條也會被單獨提高爲一個圖層,以下圖

數字1箭頭指向的地方,能夠看看,可能效果不是很明顯,你們能夠本身打開這個Layers探索下。

元素有了層疊上下文的屬性或者須要被剪裁,知足其中任意一點,就會被提高成爲單獨一層。

隱式合成

這是一種什麼樣的狀況呢,通俗意義上來講,就是z-index比較低的節點會提高爲一個單獨的途圖層,那麼層疊等級比它高的節點都會成爲一個獨立的圖層。

瀏覽器渲染流程&Composite(渲染層合併)簡單總結

缺點: 根據上面的文章來講,在一個大型的項目中,一個z-index比較低的節點被提高爲單獨圖層後,層疊在它上面的元素通通都會提高爲單獨的圖層,咱們知道,上千個圖層,會增大內存的壓力,有時候會讓頁面崩潰。這就是層爆炸

繪製

完成了圖層的構建,接下來要作的工做就是圖層的繪製了。圖層的繪製跟咱們平常的繪製同樣,每次都會把一個複雜的圖層拆分爲很小的繪製指令,而後再按照這些指令的順序組成一個繪製列表,相似於下圖👇

從圖中能夠看出,繪製列表中的指令其實很是簡單,就是讓其執行一個簡單的繪製操做,好比繪製粉色矩形或者黑色的線等。而繪製一個元素一般須要好幾條繪製指令,由於每一個元素的背景、前景、邊框都須要單獨的指令去繪製。

你們能夠在 Chrome 開發者工具中在設置欄中展開 more tools, 而後選擇Layers面板,就能看到下面的繪製列表:

在該圖中,**箭頭2指向的區域 **就是 document 的繪製列表,**箭頭3指向的拖動區域 **中的進度條能夠重現列表的繪製過程。

固然了,繪製圖層的操做在渲染進程中有着專門的線程,這個線程叫作合成線程。

分塊

  • 接下來咱們就要開始繪製操做了,實際上在渲染進程中繪製操做是由專門的線程來完成的,這個線程叫合成線程

  • 繪製列表準備好了以後,渲染進程的主線程會給合成線程發送commit消息,把繪製列表提交給合成線程。接下來就是合成線程一展宏圖的時候啦。

你想呀,有時候,你的圖層很大,或者說你的頁面須要使用滾動條,而後頁面的內容太多,多的沒法想象,這個時候須要滾動很久才能滾動到底部,可是經過視口,用戶只能看到頁面的很小一部分,因此在這種狀況下,要繪製出全部圖層內容的話,就會產生太大的開銷,並且也沒有必要。

  • 基於上面的緣由,合成線程會講圖層劃分爲圖塊(tile)
  • 這些塊的大小通常不會特別大,一般是 256 * 256 或者 512 * 512 這個規格。這樣能夠大大加速頁面的首屏展現。

首屏渲染加速能夠這麼理解:

由於後面圖塊(非視口內的圖塊)數據要進入 GPU 內存,考慮到瀏覽器內存上傳到 GPU 內存的操做比較慢,即便是繪製一部分圖塊,也可能會耗費大量時間。針對這個問題,Chrome 採用了一個策略: 在首次合成圖塊時只採用一個低分辨率的圖片,這樣首屏展現的時候只是展現出低分辨率的圖片,這個時候繼續進行合成操做,當正常的圖塊內容繪製完畢後,會將當前低分辨率的圖塊內容替換。這也是 Chrome 底層優化首屏加載速度的一個手段。

光柵化

接着上面的步驟,有了圖塊以後,合成線程會按照視口附近的圖塊來優先生成位圖,實際生成位圖的操做是由柵格化來執行的。所謂柵格化,是指將圖塊轉換爲位圖。

  • 圖塊是柵格化執行的最小單位
  • 渲染進程中專門維護了一個柵格化線程池,專門負責把圖塊轉換爲位圖數據
  • 合成線程會選擇視口附近的圖塊(tile),把它交給柵格化線程池生成位圖
  • 生成位圖的過程實際上都會使用 GPU 進行加速,生成的位圖最後發送給合成線程

運行方式以下👇

一般,柵格化過程都會使用 GPU 來加速生成,使用 GPU 生成位圖的過程叫快速柵格化,或者 GPU 柵格化,生成的位圖被保存在 GPU 內存中。

相信你還記得,GPU 操做是運行在 GPU 進程中,若是柵格化操做使用了 GPU,那麼最終生成位圖的操做是在 GPU 中完成的,這就涉及到了跨進程操做。具體形式你能夠參考下圖:

從圖中能夠看出,渲染進程把生成圖塊的指令發送給 GPU,而後在 GPU 中執行生成圖塊的位圖,並保存在 GPU 的內存中。

合成和顯示

柵格化操做完成後,合成線程會生成一個繪製命令,即"DrawQuad",併發送給瀏覽器進程。

瀏覽器進程中的viz組件接收到這個命令,根據這個命令,把頁面內容繪製到內存,也就是生成了頁面,而後把這部份內存發送給顯卡,那你確定對顯卡的原理很好奇。

看了某博主對顯示器顯示圖像的原理解釋:

不管是 PC 顯示器仍是手機屏幕,都有一個固定的刷新頻率,通常是 60 HZ,即 60 幀,也就是一秒更新 60 張圖片,一張圖片停留的時間約爲 16.7 ms。而每次更新的圖片都來自顯卡的前緩衝區。而顯卡接收到瀏覽器進程傳來的頁面後,會合成相應的圖像,並將圖像保存到後緩衝區,而後系統自動將前緩衝區後緩衝區對換位置,如此循環更新。

這個時候,心中就有點概念了,好比某個動畫大量佔用內存時,瀏覽器生成圖像的時候會變慢,圖像傳送給顯卡就會不及時,而顯示器仍是以不變的頻率刷新,所以會出現卡頓,也就是明顯的掉幀現象。


用一張圖來總結👇

迴流-重繪-合成

咱們把上面整個的渲染流水線,用一張圖片更直觀的表示👇

更新視圖三種方式

  • 迴流
  • 重繪
  • 合成

迴流

另一個叫法是重排,迴流觸發的條件就是:對 DOM 結構的修改引起 DOM 幾何尺寸變化的時候,會發生迴流過程。

具體一點,有如下的操做會觸發迴流:

  1. 一個 DOM 元素的幾何屬性變化,常見的幾何屬性有widthheightpaddingmarginlefttopborder 等等, 這個很好理解。
  2. 使 DOM 節點發生增減或者移動
  3. 讀寫 offset族、scroll族和client族屬性的時候,瀏覽器爲了獲取這些值,須要進行迴流操做。
  4. 調用 window.getComputedStyle 方法。

一些經常使用且會致使迴流的屬性和方法:

  • clientWidthclientHeightclientTopclientLeft
  • offsetWidthoffsetHeightoffsetTopoffsetLeft
  • scrollWidthscrollHeightscrollTopscrollLeft
  • scrollIntoView()scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

依照上面的渲染流水線,觸發迴流的時候,若是 DOM 結構發生改變,則從新渲染 DOM 樹,而後將後面的流程(包括主線程以外的任務)所有走一遍。

重繪

當頁面中元素樣式的改變並不影響它在文檔流中的位置時(例如:colorbackground-colorvisibility等),瀏覽器會將新樣式賦予給元素並從新繪製它,這個過程稱爲重繪。

根據概念,咱們知道因爲沒有致使 DOM 幾何屬性的變化,所以元素的位置信息不須要更新,從而省去佈局的過程,流程以下:

跳過了佈局樹建圖層樹,直接去繪製列表,而後在去分塊,生成位圖等一系列操做。

能夠看到,重繪不必定致使迴流,但迴流必定發生了重繪。

合成

還有一種狀況:就是更改了一個既不要佈局也不要繪製的屬性,那麼渲染引擎會跳過佈局和繪製,直接執行後續的合成操做,這個過程就叫合成

舉個例子:好比使用CSS的transform來實現動畫效果,避免了迴流跟重繪,直接在非主線程中執行合成動畫操做。顯然這樣子的效率更高,畢竟這個是在非主線程上合成的,沒有佔用主線程資源,另外也避開了佈局和繪製兩個子階段,因此相對於重繪和重排,合成能大大提高繪製效率。

利用這一點好處:

  • 合成層的位圖,會交由 GPU 合成,比 CPU 處理要快
  • 當須要 repaint 時,只須要 repaint 自己,不會影響到其餘的層
  • 對於 transform 和 opacity 效果,不會觸發 layout 和 paint

提高合成層的最好方式是使用 CSS 的 will-change 屬性

GPU加速緣由

好比利用 CSS3 的transformopacityfilter這些屬性就能夠實現合成的效果,也就是你們常說的GPU加速

  • 在合成的狀況下,直接跳過佈局和繪製流程,進入非主線程處理部分,即直接交給合成線程處理。
  • 充分發揮GPU優點,合成線程生成位圖的過程當中會調用線程池,並在其中使用GPU進行加速生成,而GPU 是擅長處理位圖數據的。
  • 沒有佔用主線程的資源,即便主線程卡住了,效果依然流暢展現。

實踐意義

  • 使用createDocumentFragment進行批量的 DOM 操做
  • 對於 resize、scroll 等進行防抖/節流處理。
  • 動畫使用transform或者opacity實現
  • 將元素的will-change 設置爲 opacity、transform、top、left、bottom、right 。這樣子渲染引擎會爲其單獨實現一個圖層,當這些變換髮生時,僅僅只是利用合成線程去處理這些變換,而不牽扯到主線程,大大提升渲染效率。
  • 對於不支持will-change 屬性的瀏覽器,使用一個3D transform屬性來強制提高爲合成 transform: translateZ(0);
  • rAF優化等等

參考

你不知道的 Web Workers (上)[7.8K 字 | 多圖預警]

極客時間-渲染流程

從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理

How Browsers Work: Behind the scenes of modern web browsers

史上最全!圖解瀏覽器的工做原理

W3C HTML5 標準

瀏覽器渲染流程&Composite(渲染層合併)簡單總結

瀏覽器層合成與頁面渲染優化

瀏覽器的迴流與重繪 (Reflow & Repaint)

無線性能優化:Composite

CSS GPU Animation: Doing It Right

本文使用 mdnice 排版

相關文章
相關標籤/搜索