mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
feat: 浏览器渲染原理
This commit is contained in:
498
Chapter2 - Web FrontEnd/2.45.md
Normal file
498
Chapter2 - Web FrontEnd/2.45.md
Normal file
@@ -0,0 +1,498 @@
|
||||
# 浏览器渲染原理
|
||||
|
||||
## 浏览器是如何渲染页面的
|
||||
|
||||
当浏览器的网络线程收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列。在事件循环机制下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/BrowserNetworkAndRender.png" style="zoom:20%;">
|
||||
|
||||
整个渲染流程分为多个阶段:HTML 解析、样式计算、布局、分层、绘制、分块、光栅化、画。每个阶段都有明确的输入输出,上一个阶段的输出就是下一个阶段的输入,整个流程类似流水线一样。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/BrowserRenderFullProgress.png" style="zoom:20%;">
|
||||
|
||||
下面针对每个阶段做详细的研究。
|
||||
|
||||
|
||||
|
||||
## 一、解析 HTML
|
||||
|
||||
解析的过程中遇到 CSS 便解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器会在开始解析器安,启动一个预解析线程,率先下载 HTML 中和外部 CSS、外部 JS 文件。
|
||||
|
||||
如果主线程解析到 link 标签,此时外部的 CSS 文件还没下载解析好,主线程不会等待,会继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的核心原因。
|
||||
|
||||
如果主线程解析到 script 位置,会停止解析 HTML,转而等待 JS 文件下载好,直到脚本加载和解析完成后,才能继续解析 HTML。这是因为 JS 代码执行过程可能会修改当前的 DOM,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因。
|
||||
|
||||
第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、内联样式均会包含在 CSSOM 树中
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-CSSOMDOM.png" style="zoom:20%;">
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-DOM.png" style="zoom:20%;">
|
||||
|
||||
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-CSSOM.png" style="zoom:20%;">
|
||||
|
||||
QA:
|
||||
|
||||
1. 为什么需要解析成 DOM、CSSOM?HTML 本身是字符串,无法操作,抽象成一层 JS 文档对象模型,并暴露一些 API 给 开发者,方便去操作。
|
||||
2. 为什么 HTML、CSS 都有自己对应的 DOM、CSSOM,但 JS 没有自己对应的模型?因为 CSS、HTML 是需要经常变化的,后续的逻辑会经常操作,所以有自己对应的树形模型,JS 执行一次就结束了。
|
||||
|
||||
|
||||
|
||||
### HTML 解析过程中遇到 CSS 怎么处理
|
||||
|
||||
为了提高解析效率,浏览器会启动一个预解析器率先下载和解析 CSS
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-CSSPreload.png" style="zoom:20%;">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 包含哪些样式
|
||||
|
||||
HTML 解析会生成 DOM、CSSOM。StyleSheetList 是所有样式的集合,那包含哪些样式呢?
|
||||
|
||||
- 浏览器默认样式(比如 div 独自占一行)
|
||||
- 内联样式
|
||||
- 内部样式
|
||||
- 外部样式
|
||||
|
||||
浏览器默认样式怎么体现。翻阅 Chromium 的源代码可以查看到浏览器为 html 设置的默认样式,针对 div 标签设置了 `display: block` 所以才独占一行,而不是因为是 div 所以就该占一行。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Chromium-BrowserDefaultStyle.png" style="zoom:30%;">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### HTML 解析过程中遇到 JS 代码怎么办
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-JSDownload.png" style="zoom:30%;">
|
||||
|
||||
|
||||
|
||||
浏览器渲染主线程遇到默认的 script 标签(不带 async、defer 修饰)则会暂停解析 HTML,等待 JS 下载并执行完毕后方可继续解析 HTML。
|
||||
|
||||
预解析线程可以分担下载 JS 的任务。
|
||||
|
||||
QA:为什么 JS 不能设计成像 css 那样一边解析,一边执行呢?
|
||||
|
||||
JS 可能会操作当前 DOM,DOM 不是全部的 HTML 解析完才创建的,解析多少 HTML 创建多少 DOM,所以需要暂停 HTML 解析(类似生产者消费者模型,不加锁则可能存在问题。HTML 解析就是在生产 DOM,JS 脚本可能一边在读取 DOM,可能还在增、删 DOM 在消费 DOM)
|
||||
|
||||
|
||||
|
||||
### 为什么浏览器遇到 script 会暂停解析?
|
||||
|
||||
浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的 script 标签,就会暂停 HTML 解析,先发送网络请求获取该 JS 脚本的内容,然后让 JS 引擎执行该代码。 当 JS 执行完毕后恢复 HTML 的解析,整个过程如下
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-JSDefaultBehavior.png" style="zoom:50%;">
|
||||
|
||||
解决方式:`defer ` 和 `async` 都是异步加载的外部 JS 脚本,不会阻塞页面的解析,因此这2个方案都可以规避因为大量 JS 下载影响 HTML 解析的问题
|
||||
|
||||
#### async
|
||||
|
||||
async 表示异步,当浏览器遇到带有 async 的 script 时,浏览器会采用异步的方式去解析,不会阻塞浏览器解析 HTML,一旦网络请求完成后,如果此时 HTML 还没解析完的话,立即暂停 HTML 的解析工作,让 JS 引擎执行 JS 脚本,全部 JS 执行完毕后再去解析后面的 HTML。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-JSAsync1.png" style="zoom:50%;">
|
||||
|
||||
如果遇到 async 修饰的 js 脚本,在异步下载好之后 HTML 已经解析完毕,也一样,直接执行 JS
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-JSAsync2.png" style="zoom:50%;">
|
||||
|
||||
|
||||
|
||||
#### defer
|
||||
|
||||
defer 也表示异步延迟,当浏览器遇到带有 defer 的 script 时,会异步获取该脚本,不会阻塞浏览器解析 HTML,一旦网络请求回来后,如果 HTML 解析还未完成,则会继续解析 HTML,直到 HTML 全部解析完成后才执行 JS 代码。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/HTMLParse-JSDefer.png" style="zoom:50%;">
|
||||
|
||||
如果存在多个 defer 标签修饰的 script 标签,浏览器会按照 script 顺序执行,不会破坏脚本之间的依赖关系
|
||||
|
||||
|
||||
|
||||
## 二、样式计算
|
||||
|
||||
主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出最终的样式,称为 Computed Style。
|
||||
|
||||
在这一过程中,很多预设值会变成绝对值,比如 red 会变为 rgb(255, 0, 0),相对单位也会变成绝对单位,比如 em 变成 px。这一步完成后会得到一颗带有样式的 DOM 树。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/StyleCompute.png" style="zoom:30%;">
|
||||
|
||||
什么叫计算后的样式(Computed Style):指的是元素在渲染时所采用的最终样式。
|
||||
|
||||
样式计算主要包括:CSS 属性值的计算过程(层叠、继承...)、视觉格式化模型(盒模型、包含块、BFC...)
|
||||
|
||||
这个计算涉及到:
|
||||
|
||||
- 确定声明值
|
||||
- 层叠冲突
|
||||
- 使用继承
|
||||
- 使用默认值
|
||||
|
||||
### CSS 属性计算过程
|
||||
|
||||
```css
|
||||
<div>
|
||||
<p>p</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
没有修改p 标签的样式,却可以看到他有一个颜色、字体等样式。
|
||||
|
||||
该元素上会有 css 的所有属性。在 Chrome 审查元素模式下,勾选 Computed 下的 Show all,就可以看到非常多的属性如下图:
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CSSStyleComputeDemo.png" style="zoom:20%;">
|
||||
|
||||
也就是说:我们开发的任何一个 HTML 元素,实际上在浏览器显示出来的时候都有一套完整的 css 样式。如果没有显示声明样式,大概率会采用默认值。
|
||||
|
||||
|
||||
|
||||
#### 确定声明值
|
||||
|
||||
```html
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
<div>
|
||||
<h1>H1标题</h1>
|
||||
<p>段落</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
css 代码明确了 p 标签使用红色,那么浏览器展示就会按照此属性进行展示。
|
||||
|
||||
这种开发者写的代码,叫做作者样式表。一般浏览器还会存在用户代理样式表,可以认为是浏览器内置了一份样式表。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/ComputedStyleDemo-p.png" style="zoom:30%;">
|
||||
|
||||
可以看到 p 标签在审查元素模式下,除了作者样式表中设置的 color 属性,其他的都采用了用户代理样式表的属性。比如:display...
|
||||
|
||||
|
||||
|
||||
#### 层叠冲突
|
||||
|
||||
前面看了确定声明值,但可能存在一种情况,声明的样式规则发生冲突。
|
||||
|
||||
此时会进入**解决层叠冲突**的流程,分为3个步骤:
|
||||
|
||||
1. 比较源的重要性
|
||||
2. 比如优先级
|
||||
3. 比较次序
|
||||
|
||||
|
||||
|
||||
#### 比较源的重要性
|
||||
|
||||
当不同的 css 样式来源拥有相同的声明时,此时就会根据样式表来源的重要性来确定该用哪一条样式规则。那,有几种样式表来源:
|
||||
|
||||
- 浏览器会有一个基础的样式表来给任何网页设置默认的样式。该样式被称为:用户代理样式
|
||||
- 网页的作者可以定义文档的样式,这是最常见的样式,被称为:页面作者样式
|
||||
- 浏览器的用户,可以使用自定义样式表定制使用体验。被称为用户样式
|
||||
|
||||
重要性的顺序为:页面作者样式 > 用户样式 > 用户代理样式
|
||||
|
||||
|
||||
|
||||
假设现在有页面作者样式表和用户代理样式表中存在属性冲突,那会以页面作者样式表优先。
|
||||
|
||||
```html
|
||||
p {
|
||||
color: red;
|
||||
display: inline-block;
|
||||
}
|
||||
<div>
|
||||
<h1>H1标题</h1>
|
||||
<p>段落</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CssStylePriority.png" style="zoom:25%;">
|
||||
|
||||
可以看到 p 标签的 display 属性,在页面作者样式表和用户代理样式表中同时存在时,根据源的重要性判断,最终页面作者样式表优先级更高,display 采用页面作者样式表中的属性值。
|
||||
|
||||
|
||||
|
||||
#### 比较优先级
|
||||
|
||||
如果同一个源中,样式声明一样的情况下,如何决策?此时进入了样式声明的优先级比较。
|
||||
|
||||
```html
|
||||
.container p {
|
||||
color: #00ff00;
|
||||
}
|
||||
p {
|
||||
color: red;
|
||||
display: inline-block;
|
||||
}
|
||||
<div class="container">
|
||||
<h1>H1标题</h1>
|
||||
<p>段落</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
可以看到,在上面的 css 代码中,都在页面作者样式表同一个源中,源一样,解下去根据选择器的权重来比较重要性。
|
||||
|
||||
根据选择器权重规则(ID 选择器 > 类选择器 > 类型选择器)来判断,单独一个标签选择器的 color 属性会被打败。
|
||||
|
||||
关于选择器的比较策略可以查看 [MDN CSS Specificity](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Specificity)
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CssStyleDeclarationPriority.png" style="zoom:25%;">
|
||||
|
||||
|
||||
|
||||
#### 比较次序
|
||||
|
||||
当样式表同源,权重相同的情况下,进入第三阶段:比较声明的顺序
|
||||
|
||||
```html
|
||||
.container p {
|
||||
color: #00ff00;
|
||||
}
|
||||
.container p {
|
||||
color: #0000ff;
|
||||
}
|
||||
<div class="container">
|
||||
<h1>H1标题</h1>
|
||||
<p>段落</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
都处于页面作者样式表中,选择器的权重也相同,但根据所处位置的不同,下面的样式声明会覆盖上面的值,最终采用 #0000ff。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CssStylePositionPriority.png" style="zoom:25%;">
|
||||
|
||||
样式声明冲突的情况解决了
|
||||
|
||||
|
||||
|
||||
#### 使用继承
|
||||
|
||||
层叠冲突这一步完成后,解决了相同元素被声明的多条样式规则命中后,到底该采用哪一条样式规则的问题。
|
||||
|
||||
那假设一个元素没有声明的属性,该使用什么属性值?会存在继承这个策略。
|
||||
|
||||
```html
|
||||
.container {
|
||||
color: #00ff00;
|
||||
}
|
||||
<div class="container">
|
||||
<h1>H1标题</h1>
|
||||
</div>
|
||||
```
|
||||
|
||||
只对类名为 container color 进行了设置,针对 p 标签没有任何的设置,但由于 color 可以继承,所以 p 就从最近的 div 继承了颜色。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CssStyleInherited.png" style="zoom:25%;">
|
||||
|
||||
看另一个现象
|
||||
|
||||
```html
|
||||
.container {
|
||||
color: blue;
|
||||
}
|
||||
.innerContainer {
|
||||
color: red;
|
||||
}
|
||||
<div class="container">
|
||||
<div class="innerContainer">
|
||||
<h1>H1标题</h1>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CssStyleDoubleInherited.png" style="zoom:25%;">
|
||||
|
||||
这里继承了 container、innerContainer 2个的属性值,说了继承会选择更近的一个,innerContainer 胜出。
|
||||
|
||||
|
||||
|
||||
#### 使用默认值
|
||||
|
||||
如果前面的步骤都走了,但属性值还没确定下来,就只能选用默认值了。
|
||||
|
||||
```html
|
||||
<div>
|
||||
<h1>H1标题</h1>
|
||||
</div>
|
||||
```
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CssStyleDefaultValue.png" style="zoom:25%;">
|
||||
|
||||
任何一个元素要在浏览器上渲染出来,必须具备所有的 css 属性值,但很多属性我们没有去设置,用户代理样式表中也没有设置,也无法从继承中拿到,因此最终都是使用默认值的。
|
||||
|
||||
|
||||
|
||||
## 三、布局 - layout
|
||||
|
||||
布局完成后会得到布局树。
|
||||
|
||||
布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息,比如节点的宽高、相对包含块的位置。
|
||||
|
||||
元素的尺寸和位置,会受它的包含块所影响。对于一些属性,例如 width, height, padding, margin,绝对定位元素的偏移值(比如 position 被设置为 absolute 或 fixed),当我们对其赋予百分比值时,这些值的计算值,就是通过元素的包含块计算得来。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Layout.png" style="zoom:25%;">
|
||||
|
||||
大部分时候,DOM 树和 Layout 树并非一一对应的,为什么?
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CSSLayoutTreeDisplay.png" style="zoom:25%;">
|
||||
|
||||
比如某个节点的 display 设置为 none,这样的节点就没有几何信息,因此不会生成到 layout 树。有些使用了伪元素选择器,虽然 DOM 树中并不存在这些伪元素节点,但它们需要显示在浏览器上,所以会生成到 layout 树上。还有匿名行盒、匿名块盒等等都会导致 DOM 树和 layout 树无法一一对应。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CSSLayoutTreePseudoClass.png" style="zoom:25%;">
|
||||
|
||||
```html
|
||||
div::before {
|
||||
content: '';
|
||||
}
|
||||
<div></div>
|
||||
```
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CSSPseudoClassLayout.png" style="zoom:25%;">
|
||||
|
||||
## 四、分层 Layer
|
||||
|
||||
浏览器拿到布局后的 layout tree 会思考一些事情。大多数页面不是绘制后静止的,经常会有一些动画、用户点击后一些交互处理等,需要经常刷新页面。但如果整个页面直接重新刷新,这个性能开销是很大的。能不能提高效率?能,于是现在浏览器支持了分层。
|
||||
|
||||
主线程会使用一套复杂的策略对整个布局树进行分层。
|
||||
|
||||
分层的好处在于,将来在某一个层改变后,仅会对该层进行后续处理,从而提高效率。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Layer.png" style="zoom:25%;">
|
||||
|
||||
滚动条、堆叠上下文有关的(z-index、transform、opacity )样式都会或多或少影响分层结果,也可以通过 will-change 属性更大程度的影响分层的结果。
|
||||
|
||||
```html
|
||||
<div>
|
||||
100个<p>Lorem</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Layer-Demo.png" style="zoom:25%;">
|
||||
|
||||
可以看到默认情况下浏览器对该代码分了2层。为什么滚动条需要单独设置一个 layer?因为100个 p 标签一定会超出一屏,所以会存在滚动条,且用户可能会高频滚动去查看内容,为了高效渲染,所以将滚动条单独设置了一个层。
|
||||
|
||||
假设我们已经通过技术手段得知某些内容经常会改变,但直接页面整体重刷会很耗费资源,那如何只对某一块区域设置一个 layer 呢?通过 `will-change: transform` 告诉浏览器,这块区域的 transform 属性经常会变。
|
||||
|
||||
`will-change`是一个用于通知浏览器某个元素即将发生变化的CSS属性。它可以被应用到任何元素上,用于提前告知浏览器该元素将要有哪些属性进行改变,从而优化渲染性能。
|
||||
|
||||
通过在元素上设置`will-change`属性,开发者可以明确指示浏览器对该元素进行优化处理。这样一来,浏览器可以提前分配资源和准备工作,以便在实际改变发生之前进行相应的合成操作。这样做有助于避免不必要的重绘和重排,提高页面的响应速度和动画的流畅度。
|
||||
|
||||
`will-change`属性可以接受多个属性值,表示将要改变的属性。例如,`will-change: transform`表示元素即将进行变形操作,`will-change: opacity`表示元素的透明度即将发生变化。
|
||||
|
||||
需要注意的是,`will-change`属性应在实际变化发生前的一段时间内被设置,以便浏览器有足够的时间进行准备和优化。另外,`will-change`属性并不会自动触发硬件加速,但它可以为浏览器提供一种优化渲染的提示。
|
||||
|
||||
此外,`will-change`属性的使用应谨慎,避免滥用。**只有在明确知道元素即将发生某种变化,并且这种变化对性能有影响时,才应使用`will-change`属性。过度使用`will-change`属性可能会导致浏览器进行不必要的优化,反而降低性能**。
|
||||
|
||||
总的来说,`will-change`属性是一个用于优化渲染性能的CSS属性,通过提前告知浏览器元素即将发生的变化,使浏览器能够提前进行准备和优化,从而提高页面的响应速度和动画的流畅度
|
||||
|
||||
Demo:
|
||||
|
||||
```
|
||||
div {
|
||||
will-change: transform;
|
||||
width: 200px;
|
||||
background-color: red;
|
||||
margin: 0 auto;
|
||||
}
|
||||
```
|
||||
|
||||
通过上面的 css 设置,告诉浏览器:这个 div 的 transform 经常会变,浏览器会为该元素创建一个独立的图层,将这个图层标记为“即将变换”。这样,在进行布局和绘制时,浏览器就可以更高效地处理这个元素,而无需重新计算整个渲染树。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Layer-Demo2.png" style="zoom:25%;">
|
||||
|
||||
|
||||
|
||||
## 五、绘制 - Paint
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Paint.png" style="zoom:25%;">
|
||||
|
||||
第四步的产出就是分层。第五步绘制阶段,会分别对每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来(类似 canvas 代码,告诉计算机从哪个点经过中间几个点,最后到哪个点绘制一条线,中间内容用什么颜色填充)
|
||||
|
||||
渲染主线程的工作到此为止,剩余步骤交给其他线程完成。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-MainThread-duty.png" style="zoom:25%;">
|
||||
|
||||
## 六、分块 - Tiling
|
||||
|
||||
完成绘制之后,主线程将每个图层的绘制信息提交给合成线程,剩余的工作将由合成线程完成。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Tilling.png" style="zoom:25%;">
|
||||
|
||||
合成线程首先对每个图层分块(Tiling),将其划分为更多的小区域。合成线程类似一个任务调度者,它会从线程池中拿出多个线程来完成分块的工作。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Tilling-Worker.png" style="zoom:25%;">
|
||||
|
||||
QA:浏览器渲染过程中分块的作用是什么?
|
||||
|
||||
1. 提高渲染性能:当浏览器需要渲染一个复杂的网页时,如果一次性将所有内容加载并渲染出来,可能会消耗大量的计算资源和时间,通过网页分成多个较小的块(tiling)浏览器可以并行地加载和渲染这些块,从而充分利用计算资源,提高渲染性能。
|
||||
2. 优化内存占用:如果浏览器一次性加载和渲染整个网页,可能会消耗大量的内存。通过将网页分成多个图块,浏览器可以更好的管理内存,避免不必要的内存消耗
|
||||
3. 实现懒加载:通过分块,浏览器可以实现懒加载,即只有当某个图块进入视口可见区域时,才开始加载和渲染该图块。这样可以减少不必要的加载和渲染,提高页面的加载速度和性能
|
||||
4. 方便进行并行处理和异步操作:通过将网页分成多个图块,浏览器可以更容易地进行并行处理和异步操作。例如,在移动端设备上,可以利用 GPU 进行图块的并行渲染,提高渲染效率。
|
||||
|
||||
分块 tiling 技术是浏览器渲染过程中提高性能、优化内存使用和实现懒加载的重要手段之一。
|
||||
|
||||
|
||||
|
||||
## 七、光栅化
|
||||
|
||||
分块完成后会进入光栅化阶段。光栅化是将每个块变成位图。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Raster.png" style="zoom:25%;">
|
||||
|
||||
|
||||
|
||||
合成线程将分块后的块信息交给 GPU 进程,以极高的速度完成光栅化,并优先处理靠近视口的块。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Raster-GPU.png" style="zoom:25%;">
|
||||
|
||||
|
||||
|
||||
## 八、画 - Draw
|
||||
|
||||
合成线程计算出每个位图在屏幕上的位置,交给 GPU 进行最终的呈现。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Draw.png" style="zoom:25%;">
|
||||
|
||||
合成线程拿到每个层(Layer)、每个块(Tile)的位图(bitmap)后,生成一个个指引(quad)信息。指引信息标识出每个位图应该画到屏幕上的哪个位置,此过程会考虑到旋转、缩放、变形等。
|
||||
|
||||
因为变形发生在合成线程,与渲染主线程无关,这也是 transform 效率高的本质原因。
|
||||
|
||||
合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕显示
|
||||
|
||||
|
||||
|
||||
完成过程如下
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Full-Progress.png" style="zoom:50%;">
|
||||
|
||||
|
||||
|
||||
## reflow
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Reflow.png" style="zoom:40%;">
|
||||
|
||||
比如某个业务逻辑,导致我们用 js 脚本修改了某个节点的宽度、颜色,这其实修改的是 cssom,假设业务逻辑导致操作了 DOM,DOM 节点增删了,cssom、DOM 改变了,则会触发一系列的的流程,比如重新计算样式 style、布局(layout)、分层 layer、绘制 paint、分块 tiling、光栅化 raster、画 draw、系统调用 GPU 去真正显示。
|
||||
|
||||
当渲染树 render tree 中的一部分(或者全部)因为元素的尺寸、布局、颜色、隐藏等改变而需要重新构建,这就情况被称为回流。图上的 style 这部分。
|
||||
|
||||
回流发生时,浏览器会让渲染树中受到影响的部分失效,并重新构建这部分渲染树。完成回流 reflow 后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。
|
||||
|
||||
**简单来说,reflow 就是计算元素在屏幕上确切的位置和大小并重新绘制。回流的代价远大于重绘。回流必定重绘,重绘不一定回流。**
|
||||
|
||||
|
||||
|
||||
## repaint
|
||||
|
||||
当渲染树 render tree 中的一些元素需要更新样式,但这些样式只是改变元素外观、风格,而不影响布局(位置、大小),则叫重绘。
|
||||
|
||||
**简单来说,重绘就是将渲染树节点转换为屏幕上的实际像素,不涉及重新布局阶段的位置与大小计算**
|
||||
|
||||
|
||||
|
||||
QA:为什么 transform 效率高?
|
||||
|
||||
假设在 css 中针对某个元素写了 `transform: rotate(100deg)`,transform 既不会影响布局也不会影响绘制指令,它影响的是渲染流程的最后一个阶段,图上的 draw 阶段。由于 draw 阶段发生在合成线程中,不在渲染主线程,所以 transform 的任何变化几乎不会影响主线程。同样,不管主线程如何繁忙,都不影响合成线程中 transform 的变化。
|
||||
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Browser-Reflow.png" style="zoom:40%;">
|
||||
Reference in New Issue
Block a user