Skip to content

浏览器渲染原理

🕒 Published at:

浏览器是如何渲染页面的

什么是渲染

把 html 字符串转换为像素点信息的过程就可以叫做渲染

渲染的开始

当浏览器的网络线程拿到 HTML 文档后,会产生一个渲染任务,并将其放到队列中。

在事件循环机制的作用下,渲染主线程取出队列中的渲染任务,开启渲染流程。

渲染流水线

  1. 解析 HTML 文档

  2. 样式计算

  3. 布局

  4. 分层

  5. 绘制

  6. 分块

  7. 光栅化

1. 解析 HTML - Parse HTML

最终结果是把 html 文档转换成DOM树(文档对象模型)CSSOM树(CSS 对象模型)

渲染的第一步是解析 html。

解析过程中遇到 css 解析 css遇到 js 执行 js。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载html 中的外部 css 文件外部的 js 文件

如果主线程解析到 link 位置,此时外部的 css 文件还没有下载解析好,主线程不会等待,继续后续 html 的解析。这就是css 不会阻塞 html 解析的根本原因。

如果主线程解析到 script 位置,会停止解析 html,转而执行 js 代码或者等待 js 文件下载完毕并执行后,才继续解析 html。这是因为,js 代码执行的过程中可能会修改当前构建好的 dom树,所以必须先暂停解析。这就是 js 会阻塞 html 解析的根本原因

第一步完成后,会得到 dom 树和 cssom 树,浏览器的默认样式会包含在 cssom 树中。

2. 样式计算 - Recalculate Style

主线程会遍历得到的 dom 树,依次为树中的每个节点计算出它最终的样式,称之为Computed Style

在这一过程中,很多预设值会变成绝对值,比如 red 会变成 rgb(255,0,0);相对单位会变成绝对单位,比如 em 会变成 px

3. 布局 - Layout

主线程在进行 layout 计算的时候也会生成相应的 layout 树,需要注意的是,dom 树和 layout 树并不是一一对应的

比如display: none的节点没有几何信息,因此不会生成到布局树;又比如一些伪元素在 dom 节点中不存在,但它们拥有几何信息,所以会生成到布局树中。还有匿名行盒、匿名块盒等等这些都会导致 dom 树和布局树无法一一对应。

4. 分层 - Layer

主线程会使用一套复杂的策略对整个布局树进行分层。

分层的好处是,如果一个节点经常进行变动,则只需要对该层进行后续处理,而不需要对整个树进行处理,提升了效率。

可以通过will-change属性主动影响浏览器的分层结果

5. 绘制 - Paint

主线程会为每个层单独生成绘制指令集,用于描述这一层该如何画出来

6. 分块 - Tiling

上一步完成后,主线程会将每个图层的绘制信息交给合成线程,剩余工作由合成线程完成。

合成线程首先对每个图层进行分块,将其划分成许多的小区域。这一过程是由多个线程完成的。

7. 光栅化 - Raster

分块完成后,进入光栅化阶段。

合成线程会将分块信息交给 gpu 进程,gpu 会以极高的速度进行光栅化。

光栅化的结果就是一块一块的位图

8. 画 - Draw

最后一个阶段就是画

合成线程从 gpu 进程中拿到每个块的位图后,生成一个个指引(quad)信息

指引会标识出每个位图应该画到屏幕的哪个位置,并且会考虑旋转、缩放等变形。

变形发生在合成线程,与渲染主线程无关,这就是transform效率高的原因

合成线程会把quad信息交给 gpu 进程,由 gpu 进程调用 gpu 硬件,最终完成屏幕图像的显示。

关于 reflow

reflow 的本质就是重新计算 layout 树

当进行了影响布局树的操作后,就需要重新计算布局树,触发 layout 流程。

为了避免连续多次的布局树反复计算,浏览器会合并这些操作,当 js 代码全部完成后再进行统一的布局树计算。所以,改动属性造成的 reflow 是异步完成的。

也正是因为如此,当我们在更改布局属性后立即获取它们的信息,就有可能造成无法获取到最新布局信息的结果。

浏览器最终在反复权衡之下,决定获取属性时立即 reflow。

关于 repaint

repaint 的本质就是重新根据分层信息计算了绘制指令。

当改动了可见样式后,就需要重新计算,触发 repaint 流程。

由于元素的布局信息也属于可见样式,所以 reflow 一定会引起 repaint

最后更新于: