動畫開發 --- 瀏覽器渲染相關

頁面渲染流程

咱們看幾張的經典流程圖html

瀏覽器基礎結構

image

咱們一直都在說Javascript是單線程,但瀏覽器是多線程的,在內核控制下互相配合以保持同步,主要的常駐線程有:web

GUI渲染線程

負責渲染界面,解析HTML,CSS,構建DOM和Render樹佈局繪製等。若是過程當中遇到Javascript引擎執行會被掛起線程,GUI更新保存在一個隊列中等待Javascript引擎空閒才執行;瀏覽器

Javascript引擎線程

負責解析運行Javascript;執行時間過程會致使頁面渲染加載阻塞;緩存

事件觸發線程

瀏覽器用以控制事件循環。當Javascript引擎執行過程當中觸發的事件(如點擊,請求等)會將對應任務添加到事件線程中,而當對應的事件符合觸發條件被觸發時會把對應任務添加處處理隊列的尾部等到Javascript引擎空閒時處理;性能優化

定時器觸發線程

由於Javascript引擎是單線程容易阻塞,因此須要有單獨線程爲setTimeout和setInterval計時並觸發,一樣是符合觸發條件(記時完畢)被觸發時會把對應任務添加處處理隊列的尾部等到Javascript引擎空閒時處理;W3C標準規定時間間隔低於4ms被算爲4ms。網絡

異步http請求線程

XMLHttpRequest在鏈接後瀏覽器新開線程去請求,檢測到狀態變化若是有設置回調函數會產生狀態變動事件,而後把對應任務添加處處理隊列的尾部等到Javascript引擎空閒時處理;多線程

Composite 線程

經過 GPU 執行,真正將頁面繪製並輸出到屏幕上app

頁面渲染

image

渲染流程主要步驟:異步

解析HTML生成DOM樹

整個過程是一個深度遍歷的操做,它會從當前節點從上往下逐個構建完以後纔會去構建同層級節點及其子節點,最後的結構我網上隨便找了一張圖,大概以下.ide

image

可是這個過程是能夠被CSS或者Javascript加載而阻塞,這是瀏覽器的一個處理機制,由於這二者均可能會改變DOM樹結構的變化,因此會等它們執行完以後再繼續解析

解析樣式生成CSSOM樹

這裏是跟DOM樹同時生成的,這裏包含了link, style和內聯多份不一樣的樣式整合解析成一份最終的規則,整個結構也是相似DOM樹的屬性結構,最後的結構我也是網上隨便找了一張圖,大概以下.

樣式的整合計算是個很複雜的過程,存在默認樣式,權重,繼承,僞元素,重複定義,單位換算等多種不一樣程度的難題

權重規則大概以下:

  • 元素選擇符: 1
  • class 選擇符: 10
  • id 選擇符: 100
  • 內聯樣式: 1000
  • !important 優先級最高
  1. 若是優先級相同, 則選擇最後出現的樣式;
  2. 繼承獲得的樣式的優先級最低;
  3. 嵌套選擇器優先級是相加, 例如: #A .B = 100 + 10 = 110;

實際上瀏覽器 CSS 選擇器的解析規則是從右往左的,每一步都能過濾掉些不符合規則的分支狀況, 直到找到根元素或知足條件的匹配規則的選擇器就結束這個分支的遍歷.因此儘可能避免深層嵌套 CSS, 由於尋找選擇器和計算最終樣式都會受影響的.

二者合併構建Render樹

DOM樹和CSSOM樹是根據代碼解析而成的,而代碼最終給到瀏覽器渲染的確定是視覺上的表現結構,由於它們都帶有字體,顏色,尺寸,位置甚至是否存在的樣式控制,最後的結構我也是網上隨便找了一張圖,大概以下.

image

總的來講,會遍歷整個DOM樹節點,忽略不可見節點(例如 meta,header 以及指定了屬性 display: none等節點),得出各個節點的CSS定義以及他們的從屬關係,從而去計算出每一個節點在屏幕中的位置

根據Render樹開始佈局繪製

頁面佈局以可見區域爲畫布,從左到右從上往下從根節點開始佈局.遍歷Render樹調用渲染器的API繪製節點內容填充屏幕進行像素級信息計算與繪製

實際上這一步繪製是在多個層上進行繪製

Composite

實際上在paint以後,display以前還有一個環節叫渲染層合併

image

對頁面中 DOM 元素的繪製是在多個層上進行的。在每一個層上完成繪製過程以後,瀏覽器會將全部層按照合理的順序合併成一個圖層,而後顯示在屏幕上。對於有位置重疊的元素的頁面,這個過程尤爲重要,由於一旦圖層的合併順序出錯,將會致使元素顯示異常。(下面會詳解)

迴流與重繪

這通常是解析過程或者中間樣式結構被改變須要從新進行計算佈局纔會發生,

迴流:當瀏覽器發現變化影響了佈局須要從新計算

重繪:隻影響當前元素的視覺效果而不會影響其餘元素

迴流是影響頁面性能的重要因素之一,儘可能避免

渲染完成

這不是一個按部就班的過程,瀏覽器爲了儘快渲染界面展現給用戶看,從網絡請求獲取到文檔內容的同時就開始局部渲染,而不會等全部HTML解析完纔會建立渲染.並且這是基本的流程,實際上如今主流瀏覽器會有些區別,也會優化整個渲染流程,因此這個過程實際上並無咱們想象中的這麼慢

渲染層合成

咱們就以Chrome爲例

在 Chrome 中其實有幾種不一樣的層類型:

  • RenderLayers 渲染層,這是負責對應 DOM 子樹.
  • GraphicsLayers 圖形層,這是負責對應 RenderLayers 子樹。

RenderLayers 渲染層

DOM樹每一個節點都有一個對應的渲染對象(RenderObject),當它們處於一個相同的座標空間(z軸)就會共同造成一個渲染層.,從上面知道以座標空間區分不一樣的渲染層,因此

  • 渲染層能夠有多個
  • DOM元素經過改變層疊上下文便可建立新的渲染層
  • 座標空間決定層疊上下文,但不是惟一因素,下面條件可得

而可以知足條件的狀況有:

NormalPaintLayer

  • 文檔根元素(<html>);
  • position 值爲 absolute(絕對定位)或 relative(相對定位)且 z-index 值不爲 auto 的元素;
  • position 值爲 fixed(固定定位)或 sticky(粘滯定位)的元素(沾滯定位適配全部移動設備上的瀏覽器,但老的桌面瀏覽器不支持);
  • flex (flexbox) 容器的子元素,且 z-index 值不爲 auto;
  • grid (grid) 容器的子元素,且 z-index 值不爲 auto;
  • opacity 屬性值小於 1 的元素(參見 the specification for opacity);
  • mix-blend-mode 屬性值不爲 normal 的元素;
  • 如下任意屬性值不爲 none 的元素:

    • transform
    • filter
    • perspective
    • clip-path
    • mask / mask-image / mask-border
  • isolation 屬性值爲 isolate 的元素;
  • -webkit-overflow-scrolling 屬性值爲 touch 的元素;
  • will-change 值設定了任一屬性而該屬性在 non-initial 值時會建立層疊上下文的元素(參考這篇文章);
  • contain 屬性值爲 layout、paint 或包含它們其中之一的合成值(好比 contain: strict、contain: content)的元素。

摘抄自層疊上下文

OverflowClipPaintLayer

  • overflow 不爲 visible;

NoPaintLayer

  • 不須要 paint 的 PaintLayer,好比一個沒有視覺屬性(背景、顏色、陰影等)的空 div

DOM節點和渲染對象是一一對應的,知足上面條件的渲染對象擁有獨立的渲染層,不知足的將與其第一個擁有渲染層的父元素共用同一個.簡單來講,RenderObject決定渲染內容,而RenderLayers決定繪製順序.

GraphicsLayers 圖形層

每一個 GraphicsLayer對應着一個渲染層是負責最終呈現的內容圖形層,它擁有一個圖形上下文(GraphicsContext)負責輸出該層的位圖.儲存在共享內存的位圖將做爲紋理上傳到GPU,最後由GPU將多個位圖進行合成繪製.每一個GraphicsLayer能夠獨立的進行渲染而不會相互影響,那麼將一些頻繁重繪的元素放到單獨的GraphicsLayer中就不會對整個頁面形成影響

因此 GraphicsLayer 是一個重要的渲染載體和工具,但它並不直接處理渲染層,而是處理合成層

CompositingLayer 合成層

某些特殊的渲染層會被提高至合成層(Compositing Layers),提高的前提是必須爲 SelfPaintingLayer(能夠認爲就是上面提到的 NormalPaintLayer).合成層擁有單獨的 GraphicsLayer,而其餘不是合成層的渲染層,則和其第一個擁有 GraphicsLayer 父層共用一個。

當知足如下條件的渲染層會被瀏覽器自動提高爲合成層

直接緣由(direct reason)

  • 硬件加速的 iframe 元素(好比 iframe 嵌入的頁面中有合成層)
  • video 元素
  • 覆蓋在 video 元素上的視頻控制欄
  • 3D 或者 硬件加速的 2D Canvas 元素(普通 2D Canvas 不會提高爲合成層)
  • 硬件加速的插件,好比 flash 等等
  • 在 DPI 較高的屏幕上,fix 定位的元素會自動地被提高到合成層中。但在 DPI 較低的設備上卻並不是如此,由於這個渲染層的提高會使得字體渲染方式由子像素變爲灰階
  • 有 3D transform
  • backface-visibility 爲 hidden
  • 對 opacity、transform、fliter、backdropfilter 應用了 animation 或者 transition(須要是 active 的 animation 或者 transition,當 animation 或者 transition 效果未開始或結束後,提高合成層也會失效)
  • will-change 設置爲 opacity、transform、top、left、bottom、right(其中 top、left 等須要設置明確的定位屬性,如 relative 等)

後代元素緣由

  • 有合成層後代同時自己有 transform、opactiy(小於 1)、mask、fliter、reflection 屬性
  • 有合成層後代同時自己 overflow 不爲 visible(若是自己是由於明確的定位因素產生的 SelfPaintingLayer,則須要 z-index 不爲 auto)
  • 有合成層後代同時自己 fixed 定位
  • 有 3D transfrom 的合成層後代同時自己有 preserves-3d 屬性
  • 有 3D transfrom 的合成層後代同時自己有 perspective 屬性

overlap 重疊緣由

  • 重疊或者說部分重疊在一個合成層之上。(下面隱式合成會講到)

    • 最多見和容易理解的就是元素的 border box(content + padding + border) 和合成層的有重疊
  • filter 效果同合成層重疊
  • transform 變換後同合成層重疊
  • overflow scroll 狀況下同合成層重疊。即若是一個 overflow scroll(無論overflow:auto 仍是 overflow:scrill,只要是能 scroll 便可) 的元素同一個合成層重疊,則其可視子元素也同該合成層重疊
  • 假設重疊在一個合成層之上(assumedOverlap)

    好比一個元素的 CSS 動畫效果,動畫運行期間,元素是有可能和其餘元素有重疊的。針對於這種狀況,因而就有了 assumedOverlap 的合成層產生緣由,即便動畫元素視覺上並無和其兄弟元素重疊,但由於 assumedOverlap 的緣由,其兄弟元素依然提高爲了合成層。

    須要注意的是該緣由下,有一個很特殊的狀況:

    若是合成層有內聯的 transform 屬性,會致使其兄弟渲染層 assume overlap,從而提高爲合成層。

摘抄自無線性能優化:Composite

合成層擁有單獨的GraphicsLayers,而其餘渲染層則和第一個擁有GraphicsLayers 的父層共用一個

隱式合成

部分渲染層在一些特定場景下,會被默認提高爲合成層,舉例來講

  1. 兩個絕對定位有不一樣z-index屬性的元素重疊在一塊兒,能夠從控制檯看出兩個元素跟父元素共用同一個GraphicsLayers

image

  1. 這時候若是底層元素加上transform: translateZ(0)提高至合成層以後,按理說應該會層級更高,可是實際上兩個元素都被提高到合成層,這是由於瀏覽器爲了糾正層疊順序保證視覺效果一致,隱性強行提高了本來高於底層元素層級的元素至合成層

image

層爆炸和層壓縮

層爆炸

一些合成層的條件十分隱蔽,致使產生了不在預期範圍內的合成層,達到必定數量以後會嚴重消耗性能佔用 GPU 和大量的內存資源引發頁面卡頓,造成層爆炸.咱們看下面代碼

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
    <style>
        @keyframes slide {
            from {
                transform: none;
            }

            to {
                transform: translateX(100px);
            }
        }

        .animating {
            width: 300px;
            height: 30px;
            background-color: orange;
            color: #fff;
            animation: slide 5s alternate linear infinite;
        }

        #ul {
            margin-top: -20px;
            padding: 5px;
            border: 1px solid #000;
        }

        li {
            width: 600px;
            height: 30px;
            margin-bottom: 5px;
            background-color: blue;
            color: #fff;
            position: relative;
            /* 會致使沒法壓縮:squashingClippingContainerMismatch */
            overflow: hidden;
        }

        p {
            position: absolute;
            top: 2px;
            left: 2px;
            font-size: 16px;
            line-height: 16px;
            padding: 2px;
            margin: 0;
            background-color: green;
        }
    </style>
</head>

<body>
    <div class="animating">composited animating</div>
    <ul id="ul"></ul>
</body>
<script>
    window.onload = function() {
        var $ul = document.getElementById('ul');
        for (var i = 0; i < 10; i++) {
            var ulObj = document.createElement("li");
            var pObj = document.createElement("p");
            pObj.innerText = i
            // ulObj.appendChild(pObj);
            $ul.appendChild(ulObj);
        }
    }
</script>

</html>

實驗可得10個左右已經開始卡頓,100就基本卡死了.再看控制檯如今層的狀況
image

形成層爆炸的緣由由於知足瞭如下狀況:

  1. animating元素應用了transform動畫,被提高到合成層
  2. 動畫運行期間,元素是有可能和其餘元素有重疊的(assumedOverlap),動畫元素視覺上並無和其兄弟元素重疊,但由於 assumedOverlap 的緣由,其兄弟元素依然提高爲了合成層。因此li元素都被提高到合成層,若是單單這種狀況仍是能層壓縮的
  3. li設置了overflow: hidden,當渲染層同合成層有不一樣的裁剪容器(clipping container)時,該渲染層沒法壓縮(squashingClippingContainerMismatch)

最佳方案是打破 overlap 的條件,也就是說讓其餘元素不要和合成層元素重疊。

  1. 讓其餘元素不和合成層重疊

    .animating {
      ...
      position: relative;
      z-index: 1;
    }

image

  1. 必定須要覆蓋動畫之上的話,能夠調整樣式替代裁剪樣式,去掉overflow屬性

image

層壓縮

瀏覽器會對多個渲染層同一個合成層重疊時進行優化,這些渲染層會被壓縮到一個 GraphicsLayer 中,以防止因爲重疊緣由致使可能出現的「層爆炸」,繼續用上面的例子繼續加元素
image

能夠發如今提高到合成層元素之上的多個不一樣元素會共同提高至同一個合成層,但也有不少沒法壓縮的狀況.但也不是必然,也有不少沒法壓縮的狀況

  • 沒法進行會打破渲染順序的壓縮(squashingWouldBreakPaintOrder)

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #container {
                position: relative;
                width: 420px;
                height: 80px;
                border: 1px solid black;
            }
    
            #composited {
                width: 100%;
                height: 100%;
                transform: translateZ(0);
            }
    
            #ancestor {
                -webkit-mask-image: -webkit-linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
            }
    
            #overlap-child {
                position: absolute;
                left: 0;
                top: 10px;
                bottom: 0px;
                width: 100%;
                height: 60px;
                background-color: orange;
            }
        </style>
    </head>
    
    <body>
        <div id="container">
            <div id="composited">Text behind the orange box.</div>
            <div id="ancestor">
                <div id="overlap-child"></div>
            </div>
        </div>
    </body>
    
    </html>

image

在本例中,#overlap-child 同合成層重疊,若是進行壓縮,會致使渲染順序的改變,其父元素 #ancestor 的 mask 屬性將失效,所以相似這種狀況下,是沒法進行層壓縮的。目前常見的產生這種緣由的狀況有兩種,一種是上述的祖先元素使用 mask 屬性的狀況,另外一種是祖先元素使用 filter 屬性的狀況

  • video 元素的渲染層沒法被壓縮同時也沒法將別的渲染層壓縮到 video 所在的合成層上(squashingVideoIsDisallowed)
  • iframe、plugin 的渲染層沒法被壓縮同時也沒法將別的渲染層壓縮到其所在的合成層上(squashingLayoutPartIsDisallowed)
  • 沒法壓縮有 reflection 屬性的渲染層(squashingReflectionDisallowed)
  • 沒法壓縮有 blend mode 屬性的渲染層(squashingBlendingDisallowed)
  • 當渲染層同合成層有不一樣的裁剪容器(clipping container)時,該渲染層沒法壓縮(squashingClippingContainerMismatch)

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            .clipping-container {
                overflow: hidden;
                height: 10px;
                background-color: blue;
            }
    
            .composited {
                transform: translateZ(0);
                height: 10px;
                background-color: red;
            }
    
            .target {
                position: absolute;
                top: 0px;
                height: 100px;
                width: 100px;
                background-color: green;
                color: #fff;
            }
        </style>
    </head>
    
    <body>
        <div class="clipping-container">
            <div class="composited"></div>
        </div>
        <div class="target">不會被壓縮到 composited div 上</div>
    </body>
    
    </html>

image

本例中 .target 同 合成層 .composited 重疊,可是因爲 .composited 在一個 overflow: hidden 的容器中,致使 .target 和合成層有不一樣的裁剪容器,從而 .target 沒法被壓縮。

  • 相對於合成層滾動的渲染層沒法被壓縮(scrollsWithRespectToSquashingLayer)

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            body {
                height: 1500px;
                overflow-x: hidden;
            }
    
            .composited {
                width: 50px;
                height: 50px;
                background-color: red;
                position: absolute;
                left: 50px;
                top: 400px;
                transform: translateZ(0);
            }
    
            .overlap {
                width: 200px;
                height: 200px;
                background-color: green;
                position: fixed;
                left: 0px;
                top: 0px;
            }
        </style>
    </head>
    
    <body>
        <div class="composited"></div>
        <div class="overlap"></div>
    </body>
    
    </html>

image

紅色的 .composited 提高爲了合成層,, 當滑動頁面,.overlap 重疊到 .composited 上時,.overlap 會因重疊緣由提高爲合成層,同時,由於相對於合成層滾動,所以沒法被壓縮(原文一開始說只有composited是合成,重疊以後overlap纔會提高,可是我試驗是一開始就都提高了)

  • 當渲染層同合成層有不一樣的具備 opacity 的祖先層(一個設置了 opacity 且小於 1,一個沒有設置 opacity,也算是不一樣)時,該渲染層沒法壓縮(squashingOpacityAncestorMismatch,同 squashingClippingContainerMismatch)
  • 當渲染層同合成層有不一樣的具備 transform 的祖先層時,該渲染層沒法壓縮(squashingTransformAncestorMismatch,同上)
  • 當渲染層同合成層有不一樣的具備 filter 的祖先層時,該渲染層沒法壓縮(squashingFilterAncestorMismatch,同上)
  • 當覆蓋的合成層正在運行動畫時,該渲染層沒法壓縮(squashingLayerIsAnimating),當動畫未開始或者運行完畢之後,該渲染層才能夠被壓縮(這一塊我沒實驗過,直接用原文圖片)

梳理流程

再回顧一下流程圖
image

  1. 文檔被解析成對應的DOM樹結構和CSSOM樹結構
  2. 再被解析成Render樹,每一個節點被解析成對應的RenderObject,裏面包含了關於瀏覽器怎麼渲染節點的信息,若是某些節點不被渲染則不會生成對應的RenderObject,而RenderObject的做用就是向GraphicsContext發出繪製的調用來進行元素的繪製
  3. Render樹再被解析成RenderLayers樹, RenderObject根據層疊順序解析成不一樣的RenderLayers 渲染層,
  4. RenderLayers樹解析成GraphicsLayers 樹,每一個GraphicsLayer都有本身的GraphicsContext能夠進行獨立渲染
  5. 某些特殊的RenderLayers會也被提高成CompositingLayers,擁有單獨的GraphicsLayer
  6. 開始將一層一層繪製 Layer Tree,一個 Layer 的繪製會被拆分紅許多個簡單的 繪製指令,而後按順序將這些指令組成一個繪製列表。繪製列表生成以後,渲染進程的主線程(GUI 渲染線程)會給 Composite 線程發送消息,把繪製列表給 Composite 線程開始繪製頁面。
  7. 拆分圖塊, 拆分圖塊的目的是爲了優先繪製首屏範圍覆蓋的內容,而不須要等一個 Layer 繪製完成才顯示。同時,Chrome Webkit 考慮到首屏的內容依然可能很是複雜致使繪製的時間較長,爲了改善用戶體驗,繪製圖塊時會先顯示低分辨率的圖片,當圖塊繪製完成時,再把低分辨率的圖片繪製成完整的。(這就是上面說過的瀏覽器優化渲染部分)

image

  1. 柵格化,柵格化會將圖塊(tile)轉換成位圖(bitmap),若是開啓 GPU 硬件加速,則轉換過程在 GPU 進程中完成,生成的位圖會緩存在 GPU 的內存中;若是沒有開啓 GPU 硬件加速,則由渲染進程完成,生成的位圖緩存在共享內存中。

image

  1. 合成和顯示, 在上一步的柵格化完成以後,Composite 線程會發送消息通知瀏覽器主進程從內存中獲取位圖數據,將位圖繪製到屏幕上。

image

繪製部分摘抄自瞭解瀏覽器渲染的過程和原理

CPU 與 GPU 渲染區別

CPU

中央處理器(CPU,central processing unit)做爲計算機系統的運算和控制核心,是信息處理、程序運行的最終執行單元。

在計算機體系結構中,CPU 是對計算機的全部硬件資源(如存儲器、輸入輸出單元) 進行控制調配、執行通用運算的核心硬件單元。CPU 是計算機的運算和控制核心。計算機系統中全部軟件層的操做,最終都將經過指令集映射爲CPU的操做。

GPU

圖形處理器(英語:Graphics Processing Unit,縮寫:GPU),又稱顯示核心、視覺處理器、顯示芯片,是一種專門在我的電腦、工做站、遊戲機和一些移動設備(如平板電腦、智能手機等)上作圖像和圖形相關運算工做的微處理器。

GPU使顯卡減小了對CPU的依賴,並進行部分本來CPU的工做,尤爲是在3D圖形處理時GPU所採用的核心技術有硬件T&L(幾何轉換和光照處理)、立方環境材質貼圖和頂點混合、紋理壓縮和凹凸映射貼圖、雙重紋理四像素256位渲染引擎等,而硬件T&L技術能夠說是GPU的標誌。

它不僅僅存儲圖形,並且能完成大部分圖形功能,這樣就大大減輕了CPU的負擔,提升了顯示能力和顯示速度。

從上可得,當CPU處理好位圖信息交給GPU渲染可以極大解放CPU負擔,也能快速渲染圖層.固然過分使用會致使嚴重的性能降低,內存佔用太高

相關文章
相關標籤/搜索