浏览器原理之详解渲染进程

1.前言

我们作为前端程序员,最基础的开发流程是根据 UI 原型使用HTMLCSSJavaScript等将原型设计 1:1 还原,在浏览器中渲染出来。我就特别好奇,浏览器是怎么渲染出来的,我们今天主要学习一下浏览器的渲染进程。

2.渲染进程的工作流程

渲染进程主要的工作流程:构建 DOM树样式计算布局分层绘制合成与显示

每个流程阶段的特点:

  • 每个流程阶段都有对应的输入
  • 在流程中都有处理任务;
  • 每个流程都有输出结果

3.构建DOM树

构建 DOM树是因为浏览器无法直接理解和使用HTML,所以需要将HTML转化为浏览器能够识别的结构-DOM树结构。

  • 构建 DOM树的输入就是HTML文件
  • 中间处理任务是浏览器的 HTML 的解析器进行解析
    • HTML 解析器不是等待整个文件加载完才去解析的,而是看网络加载了多少数据,HTML 解析器就会解析多少数据。其中浏览器的网络进程会实时地将数据源源不断地传递给渲染进程,然后在 HTML 解析器解析。
    • HTML 解析器内部具备分词器,分词器的作用是将开始标签和结束标签分开,那我们可以把分词器分开的东西叫做 token。
    • 接下来就是将token转化为 DOM 节点,并添加到 DOM树中。其中解析器会准备一个 token栈进行存储 token,解析器遇到开始标签将标签放到栈中,并且在 DOM 结构中创建一个标签对应的节点,按照顺序会依次入栈和创建节点,遇到标签中的内容时会直接添加到 DOM 结构中,直到遇到结束标签,结束标签不入栈,并且与之相关的开始标签出栈,那这个节点算是创建完成了
  • 输出结果是解析成的 DOM树结构。

我们可以自己实践一下,自己写个 HTML 文件,在浏览器的 Console下面打印document,我们就可以看到解析完的 DOM树结构。

HTML文件

DOM树结构

我们看到HTML 的解析器解析完的 DOM 结构与HTML 文件中的内容几乎是一样的,关键区别在于 DOM 结构是存在内存中的树结构,可以通过js 可以进行访问和修改。

面试容易问到的问题:在解析阶段如果遇到 script 标签该如何处理?会阻塞 DOM 解析么?

4.样式计算

样式计算的目的是为了计算出 DOM 节点中的每个元素的具体样式,分为以下三步:

  1. 将 CSS 文本转换成styleSheets :与 HTML 文件一样,浏览器 也需要将 CSS 文本转换成浏览器能够理解的结构-styleSheets 面试问题:CSS 引入有几种方式,不同的引入方式会影响最终styleSheets的结果么
  2. 转换样式表中的属性值,使其标准化
css 复制代码
p {
    color: red;
    font-size: 2em;
}
//标准化过程,转换成如下
p {
    color: rgb(255,0,0);
    font-size: 32px;
}

从以上可以看出,2em 被解析成了 32px,red 被解析成了 rgb(255,0,0)

  1. 计算 DOM树中每个节点的具体样式 :这其中就会涉及到 CSS 的继承规则层叠规则
  • 继承规则:CSS继承就是每个 DOM 节点都包含其父节点的样式
  • 层叠规则:层叠规则确定多个 CSS 规则作用同一个元素的时候,哪个规则最终生效。三个核心因素:来源和重要性、选择器特异性、源码顺序。这里就不展开说了...

总之样式计算的目的是计算出每个DOM 节点的样式,输入是 CSS 文本,中间处理过程是计算样式需要结合 CSS 的继承规则和层叠规则,输出结果是每个 DOM 节点的样式以及保存在 computedStyle中。

5.布局

布局阶段目的是计算出每个 DOM 节点中可见元素的具体几何位置信息。

  1. 创建布局树

    • 遍历 DOM树中的所有节点,将可见节点添加到布局树中
    • 不可见节点(比如link/head 标签,元素属性包含display:none)都不会放入布局树中
  2. 布局计算

    • 具备一颗完整的布局树,这一步就是要计算 DOM 节点的在页面的具体位置坐标

6.分层

为什么要分层,我们布局树准备好了,元素节点位置信息也计算出来了,难道不可以进行在页面绘制了么?确实是不可以,在绘制之前还需要分层这个操作,因为页面中有很多复杂的效果,可能会有 3D 变换,页面滚动,还有使用z-index进行z轴排序等,为了更好地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一颗对应的图层树。浏览器页面其实被分成了很多图层,是多个图层叠加形成了页面。

通常情况下,并不是每个节点都会有一个特定的图层,只有特定的节点才会单独渲染专用的图层,如果这个节点没有单独生成图层,那这个节点就从属于父节点的图层

那在什么情况在的节点才会生成特定的图层呢?需要满足以下条件

  • 拥有层叠上下文属性的元素:使用 z-index 在z轴方向排序的、明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等都拥有层叠上下文属性
  • 使用 clip 剪裁的地方:如果元素盒子大小不足以将内容全部包裹就会出现剪裁,相应地如果出现滚动条,那滚动条也会被单独提升为一个图层

7.绘制

  1. 生成绘制列表:在浏览器主线程根据图层生成绘制列表,生成的绘制列表会提交给合成线程
  2. 栅格化 :在某些情况下,图层会很大,需要滚动好久才会到达底部,但是用户能看到的画面也就是视口是其中一小部分,如果将图层一下绘制完产生的开销会很大。
    基于以上原因,合成线程首先将图层分成 256x256 或者 512x512 大小的图块,然后将视口部分的图块优先生成位图并绘制出来。所谓的栅格化就是将图块生成位图。通常,栅格化过程都会 使用GPU来加速生成,使用 GPU 生成的位图叫做快速栅格化,生成的位图会保存在 GPU 中.

8.合成与显示

当前视口内已完成光栅化的图块和任何已光栅化完成,合成线程会生成一个绘制命令"DrawQuad",然后将命令提交给浏览器进程。

浏览器进程中有一个 viz 的组件来接收DrawQuad命令,然后将其页面绘制内存中,最终将内存显示在屏幕上。

9.总结

结合上图,我们可以大致串一下渲染流程:

  1. 将 HTML 文件构建成浏览器可理解的DOM树
  2. 将 CSS 文本转化成浏览器可理解styleSheets结构,并计算出节点的样式
  3. 将所有可见节点放入布局树中并计算出节点在页面中的几何位置信息
  4. 根据节点中的层叠样式或者剪裁元素需要生成单独图层
  5. 浏览器主线程对图层绘制生成绘制记录表
  6. 合成线程对图层生成相应的图块,给 GPU 进程发送生成位图的命令,并保存在 GPU 中,完成光栅化
  7. 当前视口内已光栅化和任何其他位置已光栅化完成,合成线程发送命令通知浏览器进程
  8. 浏览器进程接收到命令将页面绘制内存中,最终将内存在绘制在屏幕上

参考

学习参考以下大佬输出,然后用自己的理解输出出来

《浏览器的工作原理与实践》time.geekbang.org/column/arti...

《浏览器工作原理入门教程》www.bilibili.com/video/BV1tc...

相关推荐
恋猫de小郭23 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端