從 chromium 源碼來窺探瀏覽器的渲染
你好,我是承和。
今天給大家分享一下我對于瀏覽器渲染以及優(yōu)化方面的一些理解。
傳統(tǒng)面試題
我們在各種面試題以及面試中都大概率看到過這個題目,瀏覽器在拿到數(shù)據(jù)后到最終呈現(xiàn)在?面上經(jīng)歷了哪些過程?

這絕不是瀏覽器“刷”的一下就把頁面給渲染出來了,中間經(jīng)歷了非常復雜的流程,我們一點點由淺入深來窺探瀏覽器渲染的整個過程。
傳統(tǒng)回答
首先我們可以看下比較傳統(tǒng)的回答方式,我們不去考慮 js 文件對?面解析造成的影響,在最簡單的?面中,瀏覽器僅僅拿到 HTML 文件 和 CSS 文件 ,便可以渲染出一個?面。在瀏覽器引擎中,會分別使用 HTML 解析器以及 CSS 解析器將接收到的二進制流數(shù)據(jù)轉化為瀏覽器能夠識別的 DOM 樹和 CSS 規(guī)則樹,隨后將兩者進行結合生成 Render(Layout Object)樹,瀏覽器在拿到 (Layout Object)樹后再經(jīng)歷分層,繪制等,我們才能在屏幕上看到最終的?面。
Chrome 多進程機制
我們從另一個?度,Chrome 多進程的?度也可以進行探索,大家都知道 Chrome 采用的是一個 多進程架構。 詳情參考現(xiàn)代瀏覽器架構。例如瀏覽器進程,負責瀏覽器主框架,提供一些通用的能力,GPU 進程則負責將渲染進程上傳到 GPU 中的位圖紋理進行處理隨后呈現(xiàn)到屏幕上等。而瀏覽器渲染關聯(lián)的渲染進程,當然是我們最關心的,它究竟是由哪些線程組成的,以及各個線程之間是如何通信合作來完成渲染的呢?
渲染進程組成
渲染進程主要由如下幾個線程組成:
- GUI 渲染主線程: 解析 html,css,構建 DOM 樹和 LayoutObject。
- JS 引擎線程: 執(zhí)行,解析 JS 代碼。
- 合成器線程:進行分塊操作,同時也負責接受用戶的滾動,輸入,分發(fā)回調事件等。
- 柵格化線程:將繪制命令轉換為位圖或者 GPU 能識別的紋理。
我們可以通過 chrome://tracing 記錄在一個?面渲染過程中,各個線程之間的通信:

如上所示:CRFRenderMain 表示的是渲染主線程,主要進行一些計算的操作。Compositior 表示合成器線程,主要進行合成操作。Compositior Tile Workder 表示柵格化線程,現(xiàn)代瀏覽器往往有 2-4 個柵格化線程,瀏覽器會根據(jù)資源情況合理分配柵格化線程資源。
線程間通信過程
我們從一幀渲染開始,來看各個線程之間的通信過程,如下所示:

同時Blink內核為了清晰區(qū)分各個階段,也定義了一個類DocumentLifeCycle 來確保各個階段不會發(fā)生來回的跳轉。類似于 React 當中的生命周期,一幀的渲染是一個 原子操作, 只要開始渲染便會一路執(zhí)行到底,而不會進行回滾操作。

整體的渲染流程如下:
- 合成器線程接收到Vsync信號,開始新的一幀繪制。
- 我們知道合成器線程可以處理用戶的輸入,如果一些輸入事件中存在一些回調事件,例如滾動的回調事件,那么合成器線程在上一幀收集完這些事件之后,會在當前幀將這些事件交給渲染主線程進行處理。
- 執(zhí)行 requestAnimationFrame 相關的動畫操作。
- 解析 HTML 數(shù)據(jù),形成 DOM 樹,這是 HTML 解析器(HTMLParser)的主要工作。瀏覽器接收到的html 數(shù)據(jù)也是字節(jié)流,因此要將其轉換成瀏覽器能認識及轉換的 token 標簽,在這之中主要經(jīng)歷了如下步驟:
4.1. 解碼:瀏覽器將接收的字節(jié)流(Bytes)基于編碼方式解析為字符(characters)。
4.2. 分詞:通過分詞器(詞法分析)將字符轉換為 Token,分為 Tag Token 和文本 Token。詳情可參考 vue 源碼中的模板解析過程,大部分還是相同的。
4.3. 將 tokens 標簽轉換為 nodes 節(jié)點,隨后將 nodes 節(jié)點添加至 DOM 樹上,這兩步是并行執(zhí)行的,在這期間,主要是通過棧的數(shù)據(jù)結構來進行維護(類似于常?面試題-括號匹配),當遇到開標簽時,將對應 node 推入棧中,并且添加至 DOM 樹上,當遇到文本標簽時,就直接將文本 node 添加至 DOM 樹上即可,當遇到閉合標簽時,就進行出棧操作。另外 html 是一?友好語言,對于開閉標簽不匹配的場景,或者是自定義的標簽,都有自己的處理方式,在這里就不做具體展開。

- 在有了 DOM 樹之后,就需要去計算樣式,計算樣式的主要過程就不展開了,我們主要來看 CSS 解析器的產(chǎn)物,便是 styleSheets,在控制臺使用document.styleSheets可以看到:

關于 stylesheets 的具體屬性,可參考stylesheets 詳解
我們引入 css 的方式主要有行內樣式,行內樣式表,外部樣式表(最經(jīng)常使用),這里的 stylesheets 便是一個個引入方式的最終解析產(chǎn)物。
在將各個引入方式進行解析后,我們就要將這些樣式賦予我們的 DOM 節(jié)點,瀏覽器會結合 CSS 的 繼承 , 優(yōu)先級層疊 等規(guī)則,形成 CSS 規(guī)則樹,可以通過瀏覽器的Element->Computed 查看一個DOM 節(jié)點上的具體樣式。

- Layout,計算布局,這里主要是將?面中真正需要渲染的元素在 Layout Object 樹中進行展示。
- 更新 Layer Tree,這里主要是進行一些分層的操作,例如,更新 Paint Layer Tree 及 Graphic Layer Tree,在下文會進行具體展開。
- Paint,生成繪畫指令,以及記錄需要執(zhí)行哪些繪畫調用和調用順序,將其序列化記錄進 SkPicture 數(shù)據(jù)結構中。
- Composite,計算出每個合成層在合成時所需要的數(shù)據(jù),包括位移(Translation)、縮放(Scale)、旋轉(Rotation)混合等操作的參數(shù)。
以上這些都主要是在瀏覽器主線程中進行的操作,可看出主要進行的都是些復雜度不是很高的計算操作,而 JS 的解析執(zhí)行則會放到專?的 JS 解析線程中去執(zhí)行。

- 提交至合成器線程,合成器線程主要在做的就是一個分塊操作,大家都知道,若我們對一個?面中的所有元素進行繪制的話是非常消耗性能的(因為可視區(qū)域外的渲染根本沒有必要),因此我們會首先進行分塊操作,將?面中的可視元素進行摘取并繪制。這樣可以大大地節(jié)省資源。
- 柵格化,柵格化主要進行的操作便是將上面產(chǎn)生的繪制指令轉換成 GPU 能識別的位圖或者紋理,主要有以下 2 種方式。
- a. 基于 CPU,使用 Skia 庫的軟件加速(Software Rasterization),首先繪制進位圖里,然后再作為紋理上傳到 GPU。
- b. 基于 GPU,采用硬件加速(Hardware Rasterization),這個過程是借助 OpenGL 直接在 GPU 紋理中進行繪制和光柵化,填充像素,也就是 GPU Raster。
- 提交至 GPU 進程,進行渲染和前后緩沖區(qū)的交換,將結果展示至屏幕中。
分層階段
在上面闡述的線程間通信中,我們主要忽略了分層這個過程,下面我們就主要來研究分層過程中產(chǎn)生的 3 棵樹究竟是用來干嘛的。

Layout Object Tree (布局樹)
作用:DOM 節(jié)點可以分為可視化節(jié)點(div,p),非可視化節(jié)點(script,meta,head)等。Render 樹的作用就是展現(xiàn)?面上真正需要渲染的元素,忽略掉不可?元素,例如(display:none),添加不存在 DOM 樹中但需要顯示的內容(例如偽元素)。
布局樹形成概覽

產(chǎn)生的過程如上圖所示,便是將 DOM 樹和 CSS 規(guī)則樹進行結合,忽略掉不可?元素,以及添加可?元素,來計算出各個元素在?面中的位置。
在 Chrominum 中的源碼也較簡單,通過判斷 display 來產(chǎn)生不同的 Layout Object。

映射關系
各個 html 節(jié)點與 Layout Object 的映射關系如下所示,他們都繼承自同一個基類 LayoutObject,在其 基礎上衍生出了不同的盒子模型,例如我們熟知的塊級元素,行內元素以及行內塊元素等,這些不同的類都定義了對其子元素以及兄弟元素之間是如何進行布局的,在這不做具體展開。

例子
我們也可以拿一個最簡單的布局代碼舉例,來看看他會產(chǎn)生怎樣的一顆 Render 樹。
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<meta?http-equiv="X-UA-Compatible"?content="IE=edge"?/>
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
????<title>Documenttitle>
??head>
??<body>
????<div>
??????<p>123p>
????div>
????<div>
??????1
??????<p>456p>
????div>
??body>
html>
Content Shell
我們利用的是 Chromium 官方提供的 Content Shell 命令行工具,該工具擁有 Chrome 內核,但是沒有 UI,詳情參考,運行如下命令后,便可以看到上述代碼產(chǎn)生的 Render 樹。
out/mychromium/Content\?Shell.app/Contents/MacOS/Content\?Shell?--run-web-tests?~Desktop/層演示/Layout/index.html
這里要注意的一點便是文本1外部了一個匿名塊元素,因為行內元素不能和塊級元素相鄰,所以為了布局方便,產(chǎn)生了一個匿名塊元素包裹在文本元素外圍。Paint Layer(渲染層)
一般來說,在 Render 樹的基礎上,我們會將擁有相同z 坐標空間的 Layout Objects,歸屬到同一個渲染層(Paint Layer)中。Paint Layer 最初是用來實現(xiàn)stacking context(層疊上下文),類似于畫一張藍天白云圖,我們要確定究竟是先畫藍天,還是白云。若先畫白云,再畫藍天,會出現(xiàn)白云不可?的錯誤。層疊上下文的作用亦是如此,它主要來保證?面元素以正確的順序合成。
渲染層分類
渲染層也可以主要分為以下 3 類,各個渲染層的主要形成原因如下所示:
- kNormalPaintLayer
- 根元素(HTML)
- position 值為 absolute 或 relative,且 z-index 不為 auto 的元素
- position 值為 fixed 或 sticky 的元素
- flex 容器的子元素,且 z-index 值不為 auto
- grid 容器的子元素,且 z-index 值不為 auto
- mix-blend-mode 屬性值不為 normal 的元素
- 以下任意屬性值不為 none 的元素:
- transform
- filter
- perspective
- clip-path
- mask/mask-image/mask-border
- isolation 屬性值為 isolate 的元素
- kOverflowClipPaintLayer
- overflow 不為 visible
- KNoPaintLayer
- 不需要 paint 的 PaintLayer,比如一個沒有視覺屬性(背景、顏色、陰影等)的空 div
舉例
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<meta?http-equiv="X-UA-Compatible"?content="IE=edge"?/>
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
????<title>Documenttitle>
??head>
??<body>
????<div?style="position:?absolute;?background-color:?yellow;z-index:1">
??????<div?style="opacity:?0.1">opacitydiv>
??????<div?style="filter:?blur(5px)">filterdiv>
??????<div?style="transform:?translateX(20px)">tranformdiv>
??????<div?style="mix-blend-mode:?multiply">mix-blend-modediv>
??????<div?style="overflow:?hidden">overflowdiv>
????div>
??body>
html>
我們也可以利用 Content Shell 查看上述代碼產(chǎn)生的,Paint Layer 分層情況。
out/mychromium/Content\?Shell.app/Contents/MacOS/Content\?Shell?--run-web-tests?~Desktop/層演示/Paint/index.html

可看到為各個滿足生成渲染層條件的 html 元素都形成了一個渲染層。
整體流程
整體源碼中生成渲染層的調用流程如下所示:

StyleDidChange 入口函數(shù)我們主要關心的是 LayerTypeRequired 函數(shù),在這個函數(shù)當中,我們會得到實際要產(chǎn)生哪種類型的渲染層。
void?LayoutBoxModelObject::StyleDidChange(StyleDifference?diff,
??????????????????????????????????????????const?ComputedStyle*?old_style)?{
?···
?代碼省略
?···
??if?(old_style?&&?IsOutOfFlowPositioned()?&&?Parent()?&&
??????(StyleRef().GetPosition()?==?old_style->GetPosition())?&&
??????(StyleRef().IsOriginalDisplayInlineType()?!=
???????old_style->IsOriginalDisplayInlineType()))
????Parent()->SetNeedsLayout(layout_invalidation_reason::kChildChanged,
?????????????????????????????kMarkContainerChain);
??//?判斷?Paint?Layer?的類型
??PaintLayerType?type?=?LayerTypeRequired();
??//?不為?kNoPaintLayer?
??if?(type?!=?kNoPaintLayer)?{
????if?(!Layer())?{
??????//?In?order?to?update?this?object?properly,?we?need?to?lay?it?out?again.
??????//?However,?if?we?have?never?laid?it?out,?don't?mark?it?for?layout.?If
??????//?this?is?a?new?object,?it?may?not?yet?have?been?inserted?into?the?tree,
??????//?and?if?we?mark?it?for?layout?then,?we?risk?upsetting?the?tree
??????//?insertion?machinery.
??????if?(EverHadLayout())
????????SetChildNeedsLayout();
????????//?創(chuàng)建PainterLayer?并且插入
??????CreateLayerAfterStyleChange();
????}
??}
}- LayerTypeRequired 判斷函數(shù)
這個函數(shù)的主要作用便是判斷產(chǎn)生哪種類型的 PaintLayer,這里的判斷函數(shù) ?IsStacked ?顧名思義,便是用來判斷會不會產(chǎn)生層疊上下文的。
PaintLayerType?LayoutBox::LayerTypeRequired()?const?{
????NOT_DESTROYED();
??????if?(IsStacked()?||?HasHiddenBackface()?||
??????????(StyleRef().SpecifiesColumns()?&&?!IsLayoutNGObject())?||
??????????IsEffectiveRootScroller())
????????return?kNormalPaintLayer;
??????if?(HasNonVisibleOverflow())
????????return?kOverflowClipPaintLayer;
??????return?kNoPaintLayer;
}
IsStacked 函數(shù)的核心流程如下,我們主要去判斷的就是 IsStackingContextWithoutContainment 屬性,在樣式解析過程中會有許多地方去設置這個屬性。
inline?bool?IsStacked(const?ComputedStyle&?style)?const?{
????NOT_DESTROYED();
????//?判斷定位不為?static?以及style樣式中某些屬性是否滿足堆疊上下文
????return?style.GetPosition()?!=?EPosition::kStatic?&&
???????????IsStackingContext(style);
??}
??
???inline?bool?IsStackingContext(const?ComputedStyle&?style)?const?{
????NOT_DESTROYED();
????//?This?is?an?inlined?version?of?the?following:
????//?`IsStackingContextWithoutContainment()?||
????//??ShouldApplyLayoutContainment()?||
????//??ShouldApplyPaintContainment()`
????//?The?reason?it?is?inlined?is?that?the?containment?checks?share
????//?common?logic,?which?is?extracted?here?to?avoid?repeated?computation.
????//?判斷style的?IsStackingContextWithoutContainment?屬性
????return?style.IsStackingContextWithoutContainment()?||
???????????((style.ContainsLayout()?||?style.ContainsPaint())?&&
????????????(!IsInline()?||?IsAtomicInlineLevel())?&&?!IsRubyText()?&&
????????????(!IsTablePart()?||?IsLayoutBlockFlow()));
??}
整體的調用鏈路為:

設置 IsStackingContextWithoutContainment 屬性的地方有很多,例如擁有 transform3D 屬性,便會設置 IsStackingContextWithoutContainment 為 true,我們可以去觀察下HasStackingGroupingProperty 這個函數(shù)。
void?ComputedStyle::UpdateIsStackingContextWithoutContainment(
????bool?is_document_element,
????bool?is_in_top_layer,
????bool?is_svg_stacking)?{
??if?(IsStackingContextWithoutContainment())
????return;
??//?Force?a?stacking?context?for?transform-style:?preserve-3d.?This?happens
??//?even?if?preserves-3d?is?ignored?due?to?a?'grouping?property'?being?present
??//?which?requires?flattening.?See:
??//?ComputedStyle::HasGroupingPropertyForUsedTransformStyle3D().
??//?This?is?legacy?behavior?that?is?left?ambiguous?in?the?official?specs.
??//?See?https://crbug.com/663650?for?more?details.
??//?transform?3D?樣式設置為true
??if?(TransformStyle3D()?==?ETransformStyle3D::kPreserve3d)?{
??//?設置?IsStackingContextWithoutContainment
????SetIsStackingContextWithoutContainment(true);
????return;
??}
??//?document?根元素或者含有?StackingGroupingProperty?屬性
??if?(is_document_element?||?is_in_top_layer?||?is_svg_stacking?||
??????StyleType()?==?kPseudoIdBackdrop?||?HasTransformRelatedProperty()?||
??????HasStackingGroupingProperty(BoxReflect())?||
??????HasViewportConstrainedPosition()?||?GetPosition()?==?EPosition::kSticky?||
??????HasPropertyThatCreatesStackingContext(WillChangeProperties())?||
??????/*?TODO(882625):?This?becomes?unnecessary?when?will-change?correctly?takes
??????into?account?active?animations.?*/
??????ShouldCompositeForCurrentAnimations())?{
?????//?設置?IsStackingContextWithoutContainment
????SetIsStackingContextWithoutContainment(true);
??}
}
HasStackingGroupingProperty 函數(shù)中,為各個有特殊樣式設置的元素都提升為了渲染層。
?bool?HasStackingGroupingProperty(bool?has_box_reflection)?const?{
????//?opcaity?屬性設置為?true
????if?(HasNonInitialOpacity())
??????return?true;
????//?filter?屬性設置
????if?(HasNonInitialFilter())
??????return?true;
????if?(has_box_reflection)
??????return?true;
????if?(HasClipPath())
??????return?true;
????if?(HasIsolation())
??????return?true;
??????//?mask?屬性設置
????if?(HasMask())
??????return?true;
??????//?mix-blend-mode?屬性
????if?(HasBlendMode())
??????return?true;
????if?(HasNonInitialBackdropFilter())
??????return?true;
????return?false;
??}
- Paint Layer 插入函數(shù)
在創(chuàng)建了渲染層之后,我們便要將其插入到已有渲染層樹當中,核心流程如下:
void?PaintLayer::InsertOnlyThisLayerAfterStyleChange()?{
??if?(!parent_?&&?GetLayoutObject().Parent())?{
????//?We?need?to?connect?ourselves?when?our?layoutObject()?has?a?parent.
????//?Find?our?enclosingLayer?and?add?ourselves.
????//?調用?Enclosing?Layer
????PaintLayer*?parent_layer?=?GetLayoutObject().Parent()->EnclosingLayer();
????DCHECK(parent_layer);
????//?調用?FindNextLayer
????PaintLayer*?before_child?=?GetLayoutObject().Parent()->FindNextLayer(
????????parent_layer,?&GetLayoutObject());
????//?采用頭插法進行插入
????parent_layer->AddChild(this,?before_child);
??}
??```
??代碼省略
??```
}
- 調用 EnclosingLayer,找到新創(chuàng)建的 PaintLayer 關聯(lián)的 LayoutObject 的父節(jié)點對應的 Paint Layer。
- 調用 FindNext Layer,找到要插入的 Paint Layer 在父 Paint Layer 的 Child List 中的位置 before_child。
- 采用頭插入的方法將創(chuàng)建的 Paint Layer 插入進 Child List 鏈表中。
詳情可參考:[paint layer的創(chuàng)建與插入](http://www.4k8k.xyz/article/tornmy/81737603)
GraphicLayer(合成層)
在渲染層樹的基礎上,瀏覽器又會將某些特殊的渲染層會被提升為合成層(G raphicLayers),合成層擁有單獨的 GraphicsLayer,而其他不是合成層的渲染層,則和其第一個擁有 ?GraphicsLayer 父層共用一個,如下所示:

優(yōu)勢
每個 GraphicsLayer 實際都有一個 Graphics Context ,可以理解為一個緩存。GraphicsContext會緩存該層的位圖,在下次進行繪制的時候,合成層便可以利用這些緩存直接進行繪制,而不用走繪制指令的生成等過程。位圖是存儲在共享內存中,作為紋理上傳到 GPU 中。
合成層形成舉例
注意這些都在 chorme94 以下版本中才能生效,原因后續(xù)會說明、
直接原因(direct reason)
- 有 3dtranform 相關設置 (translateZ,rotate 一些屬性的設置)
- 不同域的 iframe 元素,會使用硬件加速,使用合成層。
iframe 的主域和子域之間擁有不同的渲染進程,而渲染進程默認有一個根合成層,因此 iframe 元素會單獨形成一個合成層。
html>
<html>
??<head>
????<title>Composited?frame?testtitle>
????<style?type="text/css"?media="screen">
??????#iframe?{
????????position:?absolute;
????????left:?100px;
????????top:?100px;
????????padding:?0;
????????height:?300px;
????????width:?400px;
????????background-color:?red;
??????}
????style>
??head>
??<iframe?id="iframe"?src="composited-subframe.html"?frameborder="0">iframe>
html>
- video 元素,2d 或者 3d 的 canvas 層
- backface-visibility 為 hidden, 元素的背面朝向觀察者是否可? 。詳情參考
- 對 opacity、transform、fliter、backdropfilter 應用了 animation 或者 transition(需要是 active 的 animation 或者 transition,也就是在動畫中的元素,當 animation 或者 transition 效果未開始或結束后,提升合成層也會失效)
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<title>Test?Animationtitle>
????<style>
??????.target?{
????????width:?80px;
????????height:?80px;
????????background-color:?green;
????????-webkit-animation:?swing?5s?linear?1;
??????}
??????@-webkit-keyframes?swing?{
????????from?{
??????????transform:?rotate(0deg);
????????}
????????to?{
??????????transform:?rotate(90deg);
????????}
??????}
????style>
??head>
??<body>
????<div?class="target">div>
??body>
html>
- will-change 設置為 opacity、transform、top、left、bottom、right(其中 top、left 等需要設置明確的定位屬性,如 relative 等,默認定位 static 不會產(chǎn)生合成層。
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<title>Test?Will-changetitle>
????<style>
??????div?{
????????width:?150px;
????????height:?30px;
????????margin:?20px;
????????padding:?10px;
????????background-color:?red;
????????color:?#fff;
??????}
??????.left?{
????????position:?relative;
????????will-change:?left;
??????}
??????.top?{
????????position:?relative;
????????will-change:?top;
??????}
??????.bottom?{
????????position:?relative;
????????will-change:?bottom;
??????}
??????.right?{
????????position:?relative;
????????will-change:?right;
??????}
??????.opacity?{
????????position:?relative;
????????will-change:?opacity;
??????}
??????.transform?{
????????position:?relative;
????????will-change:?transform;
??????}
????style>
??head>
??<body>
????<div?class="left">will-change:?leftdiv>
????<div?class="top">will-change:?topdiv>
????<div?class="bottom">will-change:?bottomdiv>
????<div?class="right">will-change:?rightdiv>
????<div?class="opacity">will-change:?opacitydiv>
????<div?class="transform">will-change:?transformdiv>
??body>
html>
后代元素原因(kComboCompositedDescendants)
- 有合成層后代同時本身設置了 opactiy(小于 1)、mask、fliter、reflection 等屬性。
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<title>Test?Descendanttitle>
????<style>
??????.outer?{
????????width:?100px;
????????height:?100px;
????????border:?1px?solid?#000;
????????margin:?20px;
??????}
??????.inner?{
????????width:?50px;
????????height:?50px;
????????margin:?20px;
????????background-color:?green;
??????}
??????.composited?{
????????will-change:?transform;
??????}
??????.mask?{
????????-webkit-mask:?linear-gradient(transparent,?black);
??????}
??????.opacity?{
????????opacity:?0.5;
??????}
??????.reflection?{
????????-webkit-box-reflect:?right?10px;
??????}
??????.filter?{
????????-webkit-filter:?drop-shadow(-25px?-25px?0?gray);
??????}
????style>
??head>
??<body>
????<div?class="outer?filter">
??????filter
??????<div?class="inner?composited">innerdiv>
????div>
????<div?class="outer?reflection">
??????reflection
??????<div?class="inner?composited">innerdiv>
????div>
????<div?class="outer?opacity">
??????opacity
??????<div?class="inner?composited">innerdiv>
????div>
????<div?class="outer?mask">
??????mask
??????<div?class="inner?composited">innerdiv>
????div>
??body>
html>
- 有 3d transfrom 的合成層后代同時本身有 preserves-3d 屬性。
html>
<html>
<head>
??<style>
????.box?{
??????position:?relative;
??????height:?100px;
??????width:?100px;
??????margin:?10px;
??????left:?0;
??????top:?0;
??????background-color:?silver;
????}
????
????.preserve3d?{
??????width:?300px;
??????border:?1px?solid?black;
??????padding:?20px;
??????margin:?10px;
??????-webkit-transform-style:?preserve-3d;
????}
??style>
head>
<body>
??<div?class="preserve3d">
????This?layer?should?not?be?composited.
????<div?class="box">div>
????div>
??div>
??<div?class="preserve3d">
????This?layer?should?not?be?composited.
????<div?class="box"?style="transform:?rotate(10deg)">div>
????div>
??div>
??<div?class="preserve3d">
????This?layer?should?be?composited.
????<div?class="box"?style="transform:?rotateY(10deg)">div>
??div>
body>
html>
- 有 3dtransfrom 的合成層后代同時本身有 perspective 屬性
重疊原因(overlap)
我們重點來看當某個元素與合成層產(chǎn)生重疊時,為什么也會產(chǎn)生合成層。原有視圖中,top 和 bottom 元素共同享用父級元素的合成層資源。

當?shù)撞?bottom 因為某些原因形成合成層時,若按照原有渲染規(guī)則,top 元素會和父級元素首先被渲染,隨后是 bottom 元素的渲染,這樣就打破了層疊上下文的準則,如下所示:

因此頂部 top 元素也必須也提升為合成層,這樣才能保證按照正確的順序進行渲染。

會有如下幾個重疊原因來被迫產(chǎn)生合成層:
- filter 效果同合成層重疊
html>
<html>
??<head>
????<title>Test?Overlap?Filtertitle>
????
????<style>
??????#software?{
????????background-color:?green;
????????-webkit-filter:?drop-shadow(25px?25px?0?lightgreen);
????????position:?absolute;
????????top:?0;
????????left:?0;
????????width:?100px;
????????height:?100px;
????????color:?#fff;
??????}
??????#composited?{
????????background-color:?black;
????????position:?absolute;
????????top:?105px;
????????left:?105px;
????????width:?100px;
????????height:?100px;
????????transform:?translate3d(0,?0,?0);
????????color:?#fff;
??????}
????style>
??head>
??<body>
????<div?id="composited">compositeddiv>
????<div?id="software">overlapdiv>
??body>
html>
transform 變換后同合成層重疊
overflow scroll 情況下同合成層重疊。即如果一個 overflow scroll(不管 overflow:auto 還是 overflow:scroll,只要是能 scroll 即可) 的元素同一個合成層重疊,則其可視子元素也同該合成層重疊
假設重疊在一個合成層之上(assumedOverlap)
比如一個元素的 CSS 動畫效果,動畫運行期間,元素是有可能和其他元素有重疊的。針對于這種情況,于是就有了 assumedOverlap 的合成層產(chǎn)生原因,在本 demo 中,動畫元素視覺上并沒有和其兄弟元素重疊,但因為 assumedOverlap 的原因,其兄弟元素依然提升為了合成層。
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<title>Test?Overlap?Animationtitle>
????<style>
??????@-webkit-keyframes?slide?{
????????from?{
??????????transform:?none;
????????}
????????to?{
??????????transform:?translateX(100px);
????????}
??????}
??????.animating?{
????????width:?100px;
????????height:?100px;
????????background-color:?orange;
????????color:?#fff;
????????-webkit-animation:?slide?10s?alternate?linear?infinite;
??????}
??????.overlap?{
????????width:?100px;
????????height:?100px;
????????color:?#fff;
????????position:?relative;
????????margin:?10px;
????????background-color:?blue;
??????}
????style>
??head>
??<body>
????<div?class="animating">composited?animatingdiv>
????<div?class="overlap">overlapdiv>
??body>
html>
層壓縮
當滿足一些條件時,瀏覽器也會進行層的壓縮,以防止出現(xiàn)“太多合成層”的情況,如下所示:
html>
<head>
<style>
.composited?{
transform:?translateZ(0);
}
.box?{
??width:?100px;
??height:?100px;
}
.behind?{
??position:?absolute;
??z-index:?1;
??top:?100px;
??left:?100px;
??background-color:?blue;
}
.middle?{
??position:?absolute;
??z-index:?1;
??top:?180px;
??left:?180px;
??background-color:?lime;
}
.middle2?{
??position:?absolute;
??z-index:?1;
??top:?260px;
??left:?260px;
??background-color:?magenta;
}
.top?{
??position:?absolute;
??z-index:?1;
??top:?340px;
??left:?340px;
??background-color:?cyan;
}
div:hover?{
??background-color:?green;
??transform:translatez(0);
}
style>
head>
<body>
??<div?class="composited?box?behind">div>
??<div?class="box?middle">div>
??<div?class="box?middle2">div>
??<div?class="box?top">div>
body>
當 hover 至藍色元素時,藍色元素會被提升為合成層,而其余 3 個元素,若按照重疊原因,則會產(chǎn)生 3個合成層,但是瀏覽器會將他們劃分到一個合成層中,避免資源的過度浪費(因為創(chuàng)建一個合成層,便會產(chǎn)生一個上下文來進行記錄,這樣會加大瀏覽器的資源開銷)。

無法進行層壓縮
但有些情況下,瀏覽器也不能進行層壓縮,必須擁有不同的合成層,主要有如下幾種情況:
- 當渲染層同合成層有不同的裁剪容器(clippingcontainer)時,該渲染層無法壓縮(squashingClippingContainerMismatch)
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<title>Testtitle>
????<style>
??????.clipping-container?{
????????overflow:?hidden;
????????height:?10px;
????????background-color:?blue;
??????}
??????.composited?{
????????transform:?translateZ(0);
????????height:?10px;
????????background-color:?red;
??????}
??????.box1?{
????????position:?absolute;
????????top:?0px;
????????height:?100px;
????????width:?100px;
????????background-color:?green;
????????color:?#fff;
??????}
??????.box2?{
????????overflow:?hidden;
????????position:?relative;
????????top:?-10px;
??????}
????style>
??head>
??<body>
????<div?class="clipping-container">
??????<div?class="composited">div>
????div>
????<div?class="box1">第一個不會被壓縮到?composited?div?上div>
????<div?class="box2">第二個不會被壓縮到?composited?div?上div>
??body>
html>
上述代碼中,composited 元素與 box1,以及 box2 有不同的裁剪容器,因此 box1,box2 與composited 產(chǎn)生重疊時,會被迫單獨產(chǎn)生一個合成層,而不能進行共享。
無法進行會打破渲染順序的壓縮(ksquashingWouldBreakPaintOrder)
video 元素的渲染層無法被壓縮同時也無法將別的渲染層壓縮到 video 所在的合成層上(ksquashingVideoIsDisallowed)
iframe、plugin的渲染層無法被壓縮同時也無法將別的渲染層壓縮到其所在的合成層上(ksquashingLayoutPartIsDisallowed)
無法壓縮有 reflection 屬性的渲染層(ksquashingReflectionDisallowed)
無法壓縮有 blendmode 屬性的渲染層(ksquashingBlendingDisallowed)
源碼詳解
整體流程
整體合成層的判斷及創(chuàng)建過程如下:

UpdateAssignmentsIfNeeded 入口函數(shù)
void?PaintLayerCompositor::UpdateAssignmentsIfNeeded(
????DocumentLifecycle::LifecycleState?target_state)?{
????//?更新?dom?生命周期函數(shù)
??DCHECK(target_state?>=?DocumentLifecycle::kCompositingAssignmentsClean);
??···
??代碼省略
??···
??if?(update_type?>=?kCompositingUpdateAfterCompositingInputChange)?{
????CompositingRequirementsUpdater(*layout_view_).Update(update_root);
????CompositingLayerAssigner?layer_assigner(this);
????//?1.?創(chuàng)建?CompositingLayerMapping?(Paint?Layer?到?Graphic?Layer?的映射)
????layer_assigner.Assign(update_root,?layers_needing_paint_invalidation);
????CHECK_EQ(compositing_,?(bool)RootGraphicsLayer());
????if?(layer_assigner.LayersChanged())
??????update_type?=?std::max(update_type,?kCompositingUpdateRebuildTree);
??}
#if?DCHECK_IS_ON()
??if?(update_root->GetCompositingState()?!=?kPaintsIntoOwnBacking)?{
????AssertWholeTreeNotComposited(*update_root);
??}
#endif
??GraphicsLayerUpdater?updater;
??//?2.更新?Graphic?Layer?的一些屬性
??updater.Update(*update_root,?layers_needing_paint_invalidation);
??//?3.?根據(jù)?NeedsRebuildTree?判斷是否需要重新創(chuàng)建?Graphics?Layers
??if?(updater.NeedsRebuildTree())
????update_type?=?std::max(update_type,?kCompositingUpdateRebuildTree);
??if?(update_type?>=?kCompositingUpdateRebuildTree)?{
????GraphicsLayerVector?child_list;
????{
??
??????TRACE_EVENT0("blink",?"GraphicsLayerTreeBuilder::rebuild");
??????GraphicsLayerTreeBuilder().Rebuild(*update_root,?child_list);
????}
其中核心的便是 ComputeCompositedLayerUpdate,該函數(shù)的主要作用便是將多個渲染層映射為一個合成層。
ComputeCompositedLayerUpdate 函數(shù)

CompositingLayerAssigner::ComputeCompositedLayerUpdate(PaintLayer*?layer)?{
??CompositingStateTransitionType?update?=?kNoCompositingStateChange;
??if?(NeedsOwnBacking(layer))?{
????if?(!layer->HasCompositedLayerMapping())?{
?????//?1.?可以創(chuàng)建?Graphic?Layers
??????update?=?kAllocateOwnCompositedLayerMapping;
????}
??}?else?{
????if?(layer->HasCompositedLayerMapping())
?????//?2.?刪除?Graphic?Layers
??????update?=?kRemoveOwnCompositedLayerMapping;
????if?(!layer->SubtreeIsInvisible()?&&?layer->CanBeComposited()?&&
????????RequiresSquashing(layer->GetCompositingReasons()))?{
??????//?We?can't?compute?at?this?time?whether?the?squashing?layer?update?is?a
??????//?no-op,?since?that?requires?walking?the?paint?layer?tree.
??????update?=?kPutInSquashingLayer;
????}?else?if?(layer->GroupedMapping()?||?layer->LostGroupedMapping())?{
?????//?3.?可以進行層壓縮
??????update?=?kRemoveFromSquashingLayer;
????}
??}
??return?update;
}
在 ComputeCompositedLayerUpdate 的 needsOwnBacking 函數(shù)中,主要進行的就是合成層原因的判斷。
形成 Graphic Layers 原因
形成合成層的原因在 Chromium 的 compositing_reason 中有詳細的說明,和上面的分類差不多,主 要有直接原因,層堆疊原因以及后代元素原因。
compositing_reason.h(合成層原因概述)
namespace?blink?{
using?CompositingReasons?=?uint64_t;
#define?FOR_EACH_COMPOSITING_REASON(V)????????????????????????????????????????\
??/*?Intrinsic?reasons?that?can?be?known?right?away?by?the?layer.?*/???
??//1.?直接原因???????\
??V(3DTransform)??????????????????????????????????????????????????????????????\
??V(Trivial3DTransform)???????????????????????????????????????????????????????\
??V(Video)????????????????????????????????????????????????????????????????????\
??V(Canvas)???????????????????????????????????????????????????????????????????\
??V(Plugin)???????????????????????????????????????????????????????????????????\
??V(IFrame)???????????????????????????????????????????????????????????????????\
??V(DocumentTransitionContentElement)?????????????????????????????????????????\
??/*?This?is?used?for?pre-CompositAfterPaint?+?CompositeSVG?only.?*/??????????\
??V(SVGRoot)??????????????????????????????????????????????????????????????????\
??V(BackfaceVisibilityHidden)?????????????????????????????????????????????????\
??V(ActiveTransformAnimation)?????????????????????????????????????????????????\
??V(ActiveOpacityAnimation)???????????????????????????????????????????????????\
??V(ActiveFilterAnimation)????????????????????????????????????????????????????\
??V(ActiveBackdropFilterAnimation)????????????????????????????????????????????\
??V(AffectedByOuterViewportBoundsDelta)???????????????????????????????????????\
??V(FixedPosition)????????????????????????????????????????????????????????????\
??V(StickyPosition)???????????????????????????????????????????????????????????\???????????????????????????????????????????\
??V(OutOfFlowClipping)????????????????????????????????????????????????????????\
??V(VideoOverlay)?????????????????????????????????????????????????????????????\
??V(WillChangeTransform)??????????????????????????????????????????????????????\
??V(WillChangeOpacity)????????????????????????????????????????????????????????\
??V(WillChangeFilter)?????????????????????????????????????????????????????????\
??V(WillChangeBackdropFilter)?????????????????????????????????????????????????\
??????????????????????????????????????????????????????????????????????????????\
??/*?Reasons?that?depend?on?ancestor?properties?*/????????????????????????????\
??V(BackfaceInvisibility3DAncestor)???????????????????????????????????????????\
??/*?TODO(crbug.com/1256990):?Transform3DSceneLeaf?today?depends?only?on?the??\
?????element?and?its?properties,?but?in?the?future?it?could?be?optimized??????\
?????to?consider?descendants?and?moved?to?the?subtree?group?below.?*/?????????\
??V(Transform3DSceneLeaf)?????????????????????????????????????????????????????\
??/*?This?flag?is?needed?only?when?none?of?the?explicit?kWillChange*?reasons??\
?????are?set.?*/??????????????????????????????????????????????????????????????\
??V(WillChangeOther)??????????????????????????????????????????????????????????\
??V(BackdropFilter)???????????????????????????????????????????????????????????\
??V(BackdropFilterMask)???????????????????????????????????????????????????????\
??V(RootScroller)?????????????????????????????????????????????????????????????\
??V(XrOverlay)????????????????????????????????????????????????????????????????\
??V(Viewport)?????????????????????????????????????????????????????????????????\
??//?2.?層的堆疊????????????????????????????????????????????????????????????????????????????\
??/*?Overlap?reasons?that?require?knowing?what's?behind?you?in?paint-order????\
?????before?knowing?the?answer.?*/????????????????????????????????????????????\
??V(AssumedOverlap)???????????????????????????????????????????????????????????\
??V(Overlap)??????????????????????????????????????????????????????????????????\
??V(NegativeZIndexChildren)???????????????????????????????????????????????????\
??V(SquashingDisallowed)??????????????????????????????????????????????????????\
??????????????????????????????????????????????????????????????????????????????\
??/*?Subtree?reasons?that?require?knowing?what?the?status?of?your?subtree?is??\
?????before?knowing?the?answer.?*/???
??3.//?后代元素原因?????????????????????????????????????????\
??V(OpacityWithCompositedDescendants)?????????????????????????????????????????\
??V(MaskWithCompositedDescendants)????????????????????????????????????????????\
??V(ReflectionWithCompositedDescendants)??????????????????????????????????????\
??V(FilterWithCompositedDescendants)??????????????????????????????????????????\
??V(BlendingWithCompositedDescendants)????????????????????????????????????????\
??V(PerspectiveWith3DDescendants)?????????????????????????????????????????????\
??V(Preserve3DWith3DDescendants)??????????????????????????????????????????????\
??V(IsolateCompositedDescendants)?????????????????????????????????????????????\
??V(FullscreenVideoWithCompositedDescendants)?????????????????????????????????\
??????????????????????????????????????????????????????????????????????????????
compositing_reason.cc(映射關系)
在 compositing_reason.cc 中也描述了各個形成合成層的原因,這與我們在控制臺的 Layer 模塊中看到的合成原因一一對應。

繪制階段
當前算法流程
from?layout
??|
??v
+------------------------------+
|?LayoutObject/PaintLayer?tree?|-----------+
+------------------------------+???????????|
??|????????????????????????????????????????|
??|?PaintLayerCompositor::UpdateIfNeeded()?|
??|???CompositingInputsUpdater::Update()???|
??|???CompositingLayerAssigner::Assign()???|
??|???GraphicsLayerUpdater::Update()???????|?PrePaintTreeWalk::Walk()
??|???GraphicsLayerTreeBuilder::Rebuild()??|???PaintPropertyTreeBuider::UpdatePropertiesForSelf()
??v????????????????????????????????????????|
+--------------------+???????????????????+------------------+
|?GraphicsLayer?tree?|<------------------|??Property?trees??|
+--------------------+???????????????????+------------------+
??????|????????????????????????????????????|??????????????|
??????|<-----------------------------------+??????????????|
??????|?LocalFrameView::PaintTree()???????????????????????|
??????|???LocalFrameView::PaintGraphicsLayerRecursively()?|
??????|?????GraphicsLayer::Paint()????????????????????????|
??????|???????CompositedLayerMapping::PaintContents()?????|
??????|?????????PaintLayerPainter::PaintLayerContents()???|
??????|???????????ObjectPainter::Paint()??????????????????|
??????v???????????????????????????????????????????????????|
????+---------------------------------+???????????????????|
????|?DisplayItemList/PaintChunk?list?|???????????????????|
????+---------------------------------+???????????????????|
??????|???????????????????????????????????????????????????|
??????|<--------------------------------------------------+
??????|?PaintChunksToCcLayer::Convert()???????????????????|
??????v???????????????????????????????????????????????????|
+--------------------------------------------------+??????|
|?GraphicsLayerDisplayItem/ForeignLayerDisplayItem?|??????|
+--------------------------------------------------+??????|
??|???????????????????????????????????????????????????????|
??|????LocalFrameView::PushPaintArtifactToCompositor()????|
??|?????????PaintArtifactCompositor::Update()?????????????|
??+--------------------+???????+--------------------------+
???????????????????????|???????|
???????????????????????v???????v
????????+----------------+??+-----------------------+
????????|?cc::Layer?list?|??|???cc?property?trees???|
????????+----------------+??+-----------------------+
????????????????|??????????????|
??+-------------+--------------+
??|?to?compositor
當前 chorme 的渲染流程大致如上所示,我們在分層之后,便會在一個合成層上進行操作,來產(chǎn)生一個合成層上的繪制指令,這些繪制指令之間的順序說明了當前合成層首先繪制什么,其次繪制什么,來保證一個合成層上的多個渲染層(也就是多個層疊上下文)按照既定的規(guī)則進行有順序的渲染,如下所示:

我們會首先去渲染根層疊上下文,隨后再去繪制負 z-index 的層疊上下文,其次是一些布局元素,例如 float,塊級元素等,隨后是正 z-index 以及行內元素(因為內容是最重要的,是需要被用戶看?的)。
舉例
html>
<html?lang="en">
??<meta?http-equiv="Content-Type"?content="text/html;?Charset=UTF-8"?/>
??<head>
????<style?type="text/css">
??????*?{
????????margin:?0;
????????padding:?0;
??????}
??????div?{
????????width:?200px;
????????height:?100px;
????????text-align:?center;
????????line-height:?100px;
??????}
??????p?{
????????height:?40px;
????????line-height:?40px;
????????font-size:?20px;
????????margin-bottom:?30px;
??????}
??????.level-default?{
????????position:?absolute;
????????background:?#f5cec7;
????????top:?60px;
??????}
??????.level1?{
????????background:?#ffb284;
????????position:?absolute;
????????z-index:?2;
????????top:?160px;
??????}
??????.level2?{
????????background:?#e79796;
????????position:?absolute;
????????z-index:?1;
????????top:?260px;
??????}
??????.composite-1?{
????????position:?relative;
????????transform:?translateZ(0);
????????width:?300px;
????????height:?400px;
????????background:?#ddd;
????????margin-bottom:?20px;
??????}
????style>
??head>
??<body>
????<div?class="composite-1">
??????<p>合成層一p>
??????<div?class="level-default">默認層div>
??????<div?class="level1">渲染層1:z-index:2div>
??????<div?class="level2">渲染層2:z-index:1div>
????div>
????
??body>
html>
我們以上述代碼為例,來看 composite-1 這個合成層上的渲染層,是不是以上面提到的繪制順序進行繪制。最終的繪制產(chǎn)物如下:

查看過程
通過 Layers 工具中的 PaintProfiler 可以查看到繪制指令。

繪制指令

各指令參考也可參考 flutter 中的指令解析,https://api.flutter.dev/flutter/dart- ui/Canvas/restore.html,大致和瀏覽器中的繪制指令定義是差不多的。
性能優(yōu)化
上面講了這么多合成層形成原因,以及合成層的優(yōu)勢,我們當然要進行合理的利用,來提升我們的?面性能,可以分為如下 2 個層面:
代碼層面
- 不同 css 屬性會觸發(fā)的流程 https://csstriggers.com/,從該網(wǎng)站中可以看出修改各個屬性的值究竟會觸發(fā)哪些流程,例如我們熟悉的top,一旦修改的話,便會經(jīng)歷布局,繪制,合成等,相當于分層階段的過程都會走一遍,因此是極度損耗性能的。

而 transform 屬性的修改,我們只要將 合成層進行一些變換操作 即可,例如進行位移或者透明度的轉換,而布局和繪制的命令都可以使用緩存中的數(shù)據(jù),因此極大的提升了性能。

position 動畫:
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<meta?http-equiv="X-UA-Compatible"?content="IE=edge"?/>
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
????<title>Documenttitle>
????<style>
??????.ball-running?{
????????width:?100px;
????????height:?100px;
????????animation:?run-around?2s?linear?alternate?100;
????????background:?red;
????????position:?absolute;
????????border-radius:?50%;
??????}
??????@keyframes?run-around?{
????????0%?{
??????????top:?0;
??????????left:?0;
????????}
????????25%?{
??????????top:?0;
??????????left:?200px;
????????}
????????50%?{
??????????top:?200px;
??????????left:?200px;
????????}
????????75%?{
??????????top:?200px;
??????????left:?0;
????????}
????????100%?{
??????????top:?0px;
??????????left:?0px;
????????}
??????}
????style>
??head>
??<body>
????<div?class="ball-running">div>
??body>
html>
transform 動畫:
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<meta?http-equiv="X-UA-Compatible"?content="IE=edge"?/>
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
????<title>Documenttitle>
????<style>
??????.ball-running?{
????????width:?100px;
????????height:?100px;
????????animation:?run-around?2s?linear?alternate?100;
????????background:?red;
????????position:?absolute;
????????border-radius:?50%;
??????}
??????.ball-running?{
????????width:?100px;
????????height:?100px;
????????animation:?run-around?2s?linear?alternate?100;
????????background:?red;
????????border-radius:?50%;
??????}
??????@keyframes?run-around?{
????????0%?{
??????????transform:?translate(0,?0);
????????}
????????25%?{
??????????transform:?translate(200px,?0);
????????}
????????50%?{
??????????transform:?translate(200px,?200px);
????????}
????????75%?{
??????????transform:?translate(0,?200px);
????????}
??????}
????style>
??head>
??<body>
????<div?class="ball-running">div>
??body>
html>
性能對比
我們也可以將 left 動畫與 transform 動畫在相同的 CPU 算力下進行一個對比,如下可?,left 動畫的后半程會出現(xiàn)丟幀的情況,整個?面的 fps 也降低了很多,而這僅僅是一個元素,若是一堆元素進行重新布局的話,?面勢必會變得十分卡頓,相反transform 動畫則會保持較高的 f ps 以及不會出現(xiàn)丟幀的情況,保證了?面的流暢度。
left:

transform:

創(chuàng)建合成層
創(chuàng)建合成層的過程也相對簡單,滿足我們上面講的創(chuàng)建合成層的原因即可。
#target?{
??transform:?translateZ(0);
}
#target?{
??will-change:?transform;
}
合成層爆炸
而我們還在某些情況下,會因為操作不當,導致產(chǎn)生了過多的合成層,極大的消耗了?面的資源,而產(chǎn)生?面卡頓等問題,如下所示。
html>
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<title>Layer?Explosiontitle>
????<style>
??????@-webkit-keyframes?slide?{
????????from?{
??????????transform:?none;
????????}
????????to?{
??????????transform:?translateX(100px);
????????}
??????}
??????.animating?{
????????width:?300px;
????????height:?30px;
????????background-color:?orange;
????????color:?#fff;
????????-webkit-animation:?slide?5s?alternate?linear?infinite;
??????}
??????ul?{
????????padding:?5px;
????????border:?1px?solid?#000;
??????}
??????.box?{
????????width:?600px;
????????height:?30px;
????????margin-bottom:?5px;
????????background-color:?blue;
????????color:?#fff;
????????position:?relative;
????????/*?會導致無法壓縮:squashingClippingContainerMismatch?*/
????????overflow:?hidden;
??????}
??????.inner?{
????????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?animatingdiv>
????<ul?id="list">ul>
????<script>
??????var?template?=?function?(i)?{
????????return?[
??????????'' ,
??????????'asume?overlap,?因為?squashingClippingContainerMismatch?無法壓縮
',
??????????"",
????????].join("");
??????};
??????var?size?=?200;
??????var?html?=?"";
??????for?(var?i?=?0;?i?????????html?+=?template(i);
??????}
??????document.getElementById("list").innerHTML?=?html;
????script>
??body>
html>

上面的各個 li 元素會因為假設重疊原因,被迫提升為合成層,而他們之間擁有不同的裁剪容器,所以不能進行層壓縮,因此,每個 li 元素都產(chǎn)生了一個合成層。出現(xiàn)了層爆炸情況。。
所以我們必須打破層爆炸,第一種方法便是將動畫元素的 z-index 提升,確認將其覆蓋在 li 元素之上,這樣各個 li 元素便不會因為假設重疊而導致提升為為合成層。
- 產(chǎn)生層疊上下文,將 z-index 屬性提升:
.animating?{
??
??...
??/*?讓其他元素不和合成層重疊?*/
??position:?relative;
??z-index:?1;
}
- 去除 squashingClippingContainerMismatch:
第二種方法便是將 overflow 去除,讓各個 li 元素擁有相同的裁剪容器,這樣滿足層壓縮的條件后,便可以避免出現(xiàn)層爆炸的情況。
.box?{
????????width:?600px;
????????height:?30px;
????????margin-bottom:?5px;
????????background-color:?blue;
????????color:?#fff;
????????position:?relative;
????????/*?會導致無法壓縮:squashingClippingContainerMismatch?*/
????????overflow:?hidden;
??????}
瀏覽器層面
在瀏覽器層面,也已經(jīng)幫我們做了很大的優(yōu)化,例如在 chrome94 及以上版本中,新的瀏覽器渲染算法已經(jīng)被全量使用,與原先的渲染算法不同,新的算法會在繪制指令生成后,再進行分層,這樣可以極大的降低因為假設重疊而被迫提升為合成層的概率,下圖便是新的瀏覽器繪制算法流程:
from?layout
??|
??v
+------------------------------+
|?LayoutObject/PaintLayer?tree?|
+------------------------------+
??|?????|
??|?????|?PrePaintTreeWalk::Walk()
??|?????|???PaintPropertyTreeBuider::UpdatePropertiesForSelf()
??|?????v
??|???+--------------------------------+
??|<--|?????????Property?trees?????????|
??|???+--------------------------------+
??|??????????????????????????????????|
??|?LocalFrameView::PaintTree()??????|
??|???FramePainter::Paint()??????????|
??|?????PaintLayerPainter::Paint()???|
??|???????ObjectPainter::Paint()?????|
??v??????????????????????????????????|
+---------------------------------+??|
|?DisplayItemList/PaintChunk?list?|??|
+---------------------------------+??|
??|??????????????????????????????????|
??|<---------------------------------+
??|?LocalFrameView::PushPaintArtifactToCompositor()
??|???PaintArtifactCompositor::Update()
??|
??+---+---------------------------------+
??|???v?????????????????????????????????|
??|?+----------------------+????????????|
??|?|?Chunk?list?for?layer?|????????????|
??|?+----------------------+????????????|
??|???|?????????????????????????????????|
??|???|?PaintChunksToCcLayer::Convert()?|
??v???v?????????????????????????????????v
+----------------+?+-----------------------+
|?cc::Layer?list?|?|???cc?property?trees???|
+----------------+?+-----------------------+
??|??????????????????|
??+------------------+
??|?to?compositor
??v
上述的層爆炸例子在 chorme94 版本中的表現(xiàn)是這樣的,li 元素之間共享了一個合成層。只能說非常感謝 google 爸爸。

在 chrome94 版本中還有非常多的新功能,例如 SchedulerAPI,讓開發(fā)者可以去控制任務的優(yōu)先級,也就是在干 React 框架中, 調度模塊 在做的事情,詳情可參考卡頌大佬的文章,chrome94 的發(fā)布日志如下所示:https://blog.chromium.org/2021/10/renderingng.html
參考資料
在寫這篇文章的過程當中也參考了很多的資料,如下所示,較推薦第 2 , 3 , 6 這 3 篇文章,對于層疊上細紋,合成層以及渲染都說明的非常詳細,在此感謝各篇文章的原作者~
- 調試 chromium:https://zhuanlan.zhihu.com/p/260645423
- 層疊上下文:https://www.zhangxinxu.com/wordpress/2016/01/understand-css-stacking-context-order-z-index/
- 合成層例子:https://fed.taobao.org/blog/2016/04/26/performance-composite
- RenderNG 的數(shù)據(jù)結構:https://developer.chrome.com/blog/renderingng-data-structures/
- PaintLayer 與 GraphicLayer 創(chuàng)建:https://zhuanlan.zhihu.com/p/48515392
- 像素一生:https://docs.google.com/presentation/d/1i1Brb5FTmjStDpnUeKBphKZOJVjZzpM_rv5Wb3TMMtU/edit#slide=id.g3c8accb579_0_0
