前言
作为前端开发者,我们经常需要优化页面加载时间和渲染性能。这不仅涉及到代码层面的优化,还需要深入理解浏览器是如何工作的。
下面将从渲染和进程线程两个角度出发,深入探究从输入URL到页面完全呈现在用户面前,浏览器背后所经历的一系列复杂流程。
对齐颗粒度——名称的统一(以 MDN 上的名称为主)
-
CSSOM(CSS Object Model —— CSS 对象模型)树=== CSS 规则树(CSS Rules tree)
-
渲染树(render tree)=== 布局树(layout tree)
-
回流(reflow)=== 重排(relayout) 浏览器渲染是基于“流式布局”的模型,所以重排也叫回流
首次:布局(layout),绘制(paint)。
后续:回流(reflow),重绘(repaint)。
- 渲染树是在布局阶段生成的?还是 cssom 树和 dom 树先构建成渲染树,再进行布局?
先构建完成渲染树,再进行布局阶段。
经典问题:浏览器输入 url 回车后,发生了什么?
渲染视角
-
用户输入 URL 回车后,浏览器首先进行 DNS 查询 IP 地址,然后建立 TCP 连接,发送 HTTP 请求,获取 HTML 文件。
-
获取到 HTML 后,经过词法解析:将内容拆解成多个独立的 token,如起始标记(
<div>)、结束标记(</div>)、属性名称(id)和属性值(myDiv)。语法解析:基于生成的 token,按照 HTML 语法规则构建 DOM 树。 -
同时与 HTML 并行,浏览器还会下载并解析 CSS 文件,构建 CSSOM 树。
-
结合 DOM 树和 CSSOM 树,浏览器构建渲染树,确定哪些元素需要显示以及样式。
-
浏览器进行布局计算,确定每个元素在页面上的位置和尺寸。
-
布局完成后,进行分层,确定元素放置哪一层。
-
进入绘制阶段,①遍历渲染树来创建绘制记录,②通过获得的每个元素样式、绘制记录等信息,将这些信息转换为屏幕上的像素,这个过程被称为光栅化。
-
最后,通过合成过程,将每个图层被栅格化后的
图块(tiles),经由 GPU 展示在屏幕上。
进程线程视角
浏览器渲染的本质是多进程协作和线程调度的结果,各进程通过 IPC(跨进程通信) 实现高效协同。下面从进程和线程的视角,对上面渲染视角进行补充。
- 浏览器进程 UI线程: 接收 URL 输入后,触发 IPC通信 与 网络进程 交互。若本地 DNS 缓存未命中,通过系统 DNS 解析器递归查询 IP 地址。
- 网络进程: 建立 TCP 连接(三次握手)后,通过HTTP/HTTPS协议传输 HTML 文件。当收到 HTTP 响应时,会触发预加载扫描器(Preload Scanner),来预测和提前加载资源,减少加载时间。
- 渲染进程 GUI渲染线程: 进行 HTML、CSS 解析,布局和绘制等任务。
- 渲染进程 合成器线程: 生成多个合成图块工作线程(提高渲染效率),将页面拆分为图层(Layer)并分块(256x256像素)。
- 渲染进程 光栅线程: 线程池优先光栅化可见区域图块,转换为 GPU 可识别的位图,并上传到 GPU。
- GPU进程: 接收合成器线程的位图数据,转换为纹理(GPU中用于存储图像数据的一种结构)储存。
- 渲染进程 合成器线程: 使用纹理数据来构建合成帧,准备显示。
- GPU进程: 执行渲染操作,将帧内容绘制到帧缓冲区。
- 显示到屏幕
硬件加速到底为什么快?
光栅化分两种:软件光栅化(software rasterization)和 硬件光栅化(hardware rasterization)。
-
软件光栅化:完全依赖 CPU 单线程进行计算,通过算法将矢量图形转换为位图像素数据,上传到 GPU 内存中。(也就是上图中的光栅化流程)
-
硬件光栅化:利用 GPU 内置的 固定光栅化单元(Hardwired Rasterizer)和 并行处理能力,直接处理几何图形,转换的像素数据直接写入到 GPU 内存中(省去 CPU→GPU 的数据搬运耗时)。
固定光栅化单元(Hardwired Rasterizer) —— 这是一个直接用物理电路实现光栅化算法的模块(比如三角形覆盖测试、深度插值),类似“工厂流水线的机械臂”,专为特定任务设计,无需软件调度,直接以电路速度运行。
合成器线程的知识点补充
-
准备画下一帧前,显示器会发出一个垂直同步信号(VSync——vertical synchronization),用于协调帧的绘制节奏。
-
合成器线程 需要等待 VSync 到来后,才会将完成的 合成帧 提交给 GPU 进行显示,这一步骤确保了帧的更新与显示器的刷新周期一致,避免画面撕裂。
-
由于执行 JS 是主线程的工作,当页面合成时,合成器线程 会标记页面中绑定有事件监听的区域为 非快速滚动区域(non-fast scrollable region)。
-
如何判断点击的区域?:通过 命中测试(hit test)来查找对应的事件目标,命中测试会基于渲染过程中生成的绘制记录( paint records )查找事件发生坐标下存在的元素。
-
事件发生在 非快速滚动区域 时,需等待主线程完成事件处理,可能影响合成流畅性。否则 合成器线程 可以直接合成新的帧而不用等到主线程的响应。
-
监听相关事件时,传递 passive: true(IE不支持)参数。这样告诉渲染线程,依然需要将事件发送给主线程处理,但不需要等待。