阅读视图

发现新文章,点击刷新页面。

浏览器渲染原理

在日常面试中,我们经常会遇到这样一个问题: “在浏览器输入 URL 后,页面是如何展现出来的?”
这个看似简单的问题,其实背后涉及浏览器渲染、网络请求、解析执行等一系列复杂流程。本文将聚焦于 浏览器渲染原理,带你梳完整过程(本文主要讲解渲染进程的工作,网络进程这里不详细解释)。


整个过程可以分为两个关键步骤:

  1. 浏览器解析 URL 并发起请求 —— 浏览器首先会解析你在地址栏输入的 URL,经过 DNS 查询、TCP/TLS 连接建立等步骤,将请求发送到服务器。
  2. 解析和渲染返回结果 —— 浏览器收到服务器返回的 HTML、CSS、JS 等资源后,会依次解析、构建 DOM、CSSOM等,最终通过渲染引擎将页面呈现出来。

浏览器解析 URL

参考资料:Chrome 官方博客:导航过程中会发生什么

第 1 步:处理输入

当用户在地址栏输入内容时,浏览器进程中的 界面线程 会立即启动,判断用户输入的是搜索关键词还是网址,并据此决定:要么将请求定向到搜索引擎,要么直接访问指定网站。

第 2 步:开始导航

用户按下 Enter 键后,UI 线程会发起网络请求以获取网站内容。此时,标签页角落会显示加载旋转图标,提示页面正在加载。

网络线程会按照网络协议处理请求,包括 DNS 查询 和建立 TLS 连接(如果是 HTTPS)。

若服务器返回 HTTP 301 等重定向响应,网络线程会通知 UI 线程,并根据重定向地址发起新的请求,完成导航流程。

第 3 步:读取响应

网络线程开始接收服务器返回的响应数据(载荷),并查看前几个字节以判断内容类型。虽然响应头中的 Content-Type 应指明数据类型,但由于可能缺失或错误,浏览器会执行 MIME 类型嗅探

  • 如果响应是 HTML,数据会传递给渲染进程进行渲染。
  • 如果是 ZIP 或其他文件,则交由下载管理器处理。

同时,浏览器会进行安全检查:

第 4 步:查找渲染进程

完成安全检查后,如果网络线程确认导航可继续,它会通知界面线程,界面线程随后查找或启动合适的 渲染进程,以准备渲染网页。

优化机制:由于网络请求可能需要数百毫秒,浏览器会在第 2 步发起请求时,就尝试并行启动渲染进程。这意味着当数据到达时,渲染进程通常已处于待机状态。


解析和渲染返回结果

当数据和渲染进程都准备就绪后,浏览器进程会向渲染进程发送一次 提交导航(Commit Navigation) 的 IPC 信号,并将来自服务器的 HTML 数据流交给渲染进程。
渲染进程在接收到该指令后,会将“解析 HTML”的工作加入自身 渲染主线程(Main Thread) 的消息队列。

在事件循环机制的调度下,渲染主线程从队列中取出该任务并开始执行,正式进入渲染流程。

整体的渲染步骤如下图所示:

image.png


第 1 步:解析 HTML 与构建 DOM / CSSOM

服务器返回的 HTML 本质上是 字节流,浏览器会将其解码为 字符串,再进一步解析成可操作的数据结构——这就是我们熟悉的 DOM 树。同样,CSS 也会被解析成对应的 CSSOM 树

在解析过程中:

  • 遇到 HTML 标签 ⇒ 主线程将其转换为 DOM 节点并加入 DOM 树中(树的根节点是 document)。
  • 遇到 CSS ⇒ 根据层叠规则解析生成 CSSOM 树(根节点为 StyleSheetList)。

在浏览器解析 HTML 时,即使遇到 <link><style> 标签,主线程也会继续向下解析 HTML,而不会被阻塞。这是因为浏览器会启动一个 预解析线程(Preload Scanner),提前发现并下载 CSS 等外部资源。 CSS 的下载和准备工作在这个预解析线程中完成,不会占用主线程,所以CSS 不会阻塞 HTML 解析。只有当 HTML 构建完成、CSS 下载完毕后,浏览器才会利用 CSS 生成 CSSOM 树,与 DOM 树结合形成 渲染树,用于后续页面绘制。 通过这种方式,浏览器实现了 DOM 构建与 CSS 下载的并行,提高了解析效率,同时保证页面渲染的正确性。

当浏览器解析到 <script> 标签时,会暂停 DOM 的构建,等待外部脚本下载完成,并在主线程中完成解析与执行。
原因在于:JavaScript 有能力通过 document.write、DOM API 等方式直接修改正在构建的 DOM 结构
如果不暂停解析,一边构建 DOM、一边执行 JS,就可能导致 DOM 状态出现不一致。 因此,为了保证 DOM 构建过程的正确性和可预测性,浏览器必须中断 HTML 解析,优先执行脚本。这就是 JavaScript 会阻塞 DOM 构建的根本原因

在解析结束之后,会得到:DOM 树CSSOM 树


第 2 步:样式计算

主线程会遍历构建完成的 DOM 树,并为树中每一个节点计算出它的最终样式,这个过程称为 样式计算(Computed Style)
样式计算结束后,我们获得的是一棵“附带最终样式信息的 DOM 树”,为后续布局阶段提供基础。

关于样式计算的具体细节,可以参考我另一篇文章:样式计算


第 3 步:布局

布局(Layout)是浏览器确定页面中每个元素几何位置和大小的过程。主线程会遍历 DOM 树,结合计算后的样式信息,生成包含 坐标、边界框尺寸 等几何信息的 布局树(Layout Tree)

布局树与 DOM 树的结构类似,但只包含 实际在页面中显示的内容

  • 应用 display: none 的元素不会出现在布局树中,因为它们没有几何信息。
  • 应用 visibility: hidden 的元素仍然会在布局树中保留位置和尺寸信息。
  • 伪元素(如 p::before { content: "Hi!"; })虽然不在 DOM 树中,但具有几何信息,因此会出现在布局树中。
  • 其他情况如匿名块盒、匿名行盒等,也会导致布局树与 DOM 树不完全一一对应。

因此,大多数情况下,DOM 树和布局树并非严格对应,布局树只关注用于渲染的几何信息,而非完整的 DOM 结构。


第 4 步:分层

为了确定哪些元素需要位于哪些层,浏览器主线程会遍历 布局树 并生成 层树(Layer Tree) 。在 Chrome DevTools 的性能面板中,这一阶段通常显示为“更新层树”。

分层的核心目的是 提升渲染效率。浏览器会根据提示提前为元素分配独立层,优化动画或滚动等操作的渲染效率。
将页面划分为独立的层后,如果某个层的内容发生变化,浏览器只需要重绘该层,而无需重新渲染整个页面。

如下图所示:
image.png

某些 CSS 属性和布局特性会自动触发分层,例如:

  • transformopacityfilter
  • position: fixed / sticky
  • overflow: scroll / auto
  • z-index / 堆叠上下文

此外,如果希望浏览器为某些元素创建独立层,可以使用 will-change 属性向浏览器发出提示:

.menu {
  will-change: transform;
}
补充说明
  • 分层不会改变 DOM 结构或布局,只是为渲染过程划分优化单位。
  • 虽然分层可以提高性能,但过度分层会增加 GPU 内存占用,因此应谨慎使用 will-change,仅对频繁变化的元素设置。

第 5 步:绘制(生成绘制指令)& 分块

  • 绘制阶段(主线程)

    • 主线程会为每个独立层生成对应的 绘制指令集(Paint Commands),用于描述这一层应该如何渲染,包括颜色、边框、文字、图片等。
    • 绘制指令通常以矢量或命令序列的形式存在,而不是直接生成位图,这样可以提高渲染效率和重绘灵活性。
    • 绘制完成后,主线程将每个层的绘制信息 提交给合成线程(Compositor Thread),主线程任务结束。
  • 分块阶段(合成线程)

    • 合成线程为了优化 GPU 渲染,会将每个图层划分为更小的 绘制块(Tiles)

    • 分块的优势:

      • 局部更新:只重绘变化的块,减少不必要的 GPU 负担。
      • 并行处理:可从线程池中获取多个线程同时处理不同块,提高处理速度。
      • 内存优化:分块渲染可以让 GPU 更好地管理显存,避免一次性加载大图层导致内存峰值过高。

image.png


第 6 步:光栅化

在渲染流程中,合成线程会将页面的图层信息(Layer / Tile)交给 GPU 进程,以高效完成 光栅化

光栅化的本质是将矢量图形、文本、图层等内容转化为 像素位图(bitmap) ,方便最终在屏幕上显示。

  • GPU 进程通常会开启多个线程并行处理不同的瓦片(Tile),提升渲染效率。
  • 浏览器会优先处理靠近视口(Viewport)区域的块,以确保用户能尽快看到可视内容,提高 首屏渲染性能(FCP / First Contentful Paint)
  • 光栅化完成后,每个图层或瓦片都会生成一块位图,为最终的 合成(Compositing) 做准备。

第 7 步:页面绘制

在光栅化完成后,合成线程(Compositor Thread) 会拿到每个图层(Layer)和瓦片(Tile)的位图,并生成对应的 绘制指引(quad)

  • Quad 的作用:指示每块位图在屏幕上的显示位置,同时包含旋转、缩放、透明度等变换信息。
  • 高效变换:变换(transform)操作发生在合成线程,而不需要经过渲染主线程。这就是 CSS transformopacity 能够实现 GPU 加速、渲染性能高的原因。

合成线程生成 quad 后,会将其提交给 GPU 进程。GPU 进程通过系统调用与 GPU 硬件交互,完成最终的像素合成,将页面内容呈现在屏幕上。

补充总结:

  • 渲染主线程负责构建 DOM、CSSOM、渲染树、布局和绘制图层内容;
  • 合成线程只负责图层组合和变换操作;
  • GPU 负责将最终像素输出到显示器,实现高效渲染。

流程总结:

第一步 浏览器解析 URL:

image.png

第二步:解析和渲染:

image.png

❌