浏览器的渲染流程
1. 将 HTML 解析为 DOM 树
由于浏览器并不能理解 HTML 文件, 故而当网络进程将服务器返回的 HTML 传输给渲染进程时, 渲染进程也会逐步解析 HTML 文件, 构造一个浏览器能够理解的 DOM 树
2. 将 CSS 解析为 CSSOM 树
浏览器并不能理解 CSS 文件的内容, 故而当网络进程将服务器返回的 CSS 文件传输给渲染进程时, 渲染进程也会解析 CSS 文件, 构造一个 CSSOM 树, 并将对应的样式标准化, 使浏览器能够理解该内容
3. 合并 DOM 和 CSSOM 为布局树
4. 绘制
4.1 分层
4.1.1 渲染对象 Render Object
Render Object 上实现了将 DOM 绘制进位图的方法, 负责绘制 DOM 的可见内容. 每个 Render Object 和 DOM 节点一一对应, 且 Render Object 和 DOM 一样是树形的
4.1.2 渲染层 Render Layer
每一个 z-index 不同的层叠上下文元素(或被裁剪过的元素)划分为一个渲染层
而不是层叠上下文的元素(或者没有被裁剪过的元素)则会与离其最近的祖先层叠上下文元素共享同一个渲染层
4.1.3 合成层/图层 Graphics Layer 与 Graphics Context
当一个渲染层满足如下的常见条件时, 会被提升为 Graphics Layer
- 3D transfroms: translate3d, translateZ 等
- video, canva, iframe 等元素
- 通过 Element.animate() 实现的 opacity 动画转换
- 通过 CSS 动画实现的 opacity 动画转换
- position: fixed
- 具有 will-change 属性
- 对 opacity, transform, fliter, backdropfilter 应用了 animation 或 transition
4.2 绘制图层
4.2.1 生成绘制列表
渲染进程会将一个图层的绘制拆分为很多小的绘制指令, 然后再将这些指令按照顺序组成一个待绘制列表
在浏览器中, 可以通过开发者工具查看:
生成完待绘制列表后, 渲染进程会将待绘制列表从主线程提交给其渲染进程中的合成线程
5. 合成
5.1 栅格化
有些图层可能会很大, 而用户又只能看到很小一部分. 如果直接对所有的图层进行绘制的话, 就会产生太大的开销, 而且也没有必要.
基于这个原因, 合成线程不会一次性就将图层绘制完毕, 而是优先绘制图层的一小部分(以视口为基准), 栅格化过程如下(栅格化操作通常会使用 GPU 进行加速完成)
- 合成线程将每个图层划分为多个图块(tile)
- 合成线程优先为可视图块生成位图, 当使用 GPU 加速时, 通常在 GPU 中生成位图, 生成的位图也保存在 GPU 的内存中
5.2 提升到合成层的好处
我们构造一个在页面中不断移动的元素
Code
<html>
<style>
@keyframes move{
0%{
top: 0;
left: 0;
}
50%{
top: 500px;
left: 500px;
}
75%{
top: 0;
left: 400px;
}
100% {
top: 400px;
left: 0px;
}
}
@keyframes opacity{
0%{
background-color: aliceblue;
}
50%{
background-color: aqua;
}
100%{
background-color:blueviolet;
}
}
#composited{
width: 200px;
height: 200px;
background: red;
position: absolute;
left: 0;
top: 0;
z-index: 3;
}
.both{
animation: move 1s infinite, opacity 1s infinite;
}
.move{
animation: move 1s infinite;
}
</style>
<div id="composited" class="both">
composited - animation
</div>
</html>
当将移动的元素提升到合成层时, 可以看到绘制次数(Paint Count)都只有一次
而该对应的元素没有提升到合成层时, 我们看到: 不仅 #document 的绘制次数大量增加, 而且 GPU 的内存占用也比之前多不少
6. 页面渲染的阻塞
6.1 CSS 阻塞页面的渲染
当对 CSS 解析没有完成时, DOM 无法与 CSSOM 形成 Layout Tree, 故而页面无法渲染
注: CSS 的解析和 HTML 的解析是同步进行的, CSSOM 的构建不会阻塞 DOM 的构建. 只是 CSSOM 没有构建完毕会使得 Layout Tree 的构建被阻塞, 最终表现为页面的展示被阻塞
6.2 JavaScript 阻塞页面的渲染
- 当 JavaScript 没有加载完成时, 因为 script 代码有可能会更改 DOM 树结构, 故而 HTML 并不会继续解析, 而是等待 JavaScript 代码执行完毕之后再进行解析
- JavaScript 代码加载完成之后需要等待 CSS 代码的加载完成, 因为 JavaScript 代码执行时可能要操作 Style 样式, 当 CSS 没有加载完成时, JavaScript 代码不会执行
解决方法:
- 在 script 标签上添加
sync
或defer
字段sync
: JavaScript 代码加载完成后, 立马执行对应的 JavaScript 代码defer
: JavaScript 代码加载完成后, 等待页面渲染完毕后再执行
- 采用预解析线程: 当 HTML 代码中需要加载 JavaScript 或 CSS 时, 预解析线程会对这些文件进行提前下载