从url输入到页面渲染(二):解析和渲染过程详解

241 阅读8分钟

上回书说到,彼时的页面,上有解析连天网,下有TCP断连诀,浏览器接收HTML,欲给页面画上朗朗乾坤,传说,浏览器在解析之时,曾言道...

注:本文为上一篇《从url输入到页面渲染:深挖浏览器渲染的完整过程》中,“浏览器解析与渲染页面”内容部分的详细解读,上一篇文章对本文内容无影响,可放心食用。

我获取内容,并以七步完成渲染:

第一步:解析HTML(HTML Parser),构建HTML树。

第二步:解析CSS(CSS Parser),构建CSSOM树。

第三步:合成渲染树(Render Tree)

第四步:布局(Layout)

第五步:分层(Layering)

第六步:绘制(Painting)

第七步:合成显示(Display)

过程详解如图所示:

ae7aa55c42430c2139c5db346e112a57.png

接下来,是对于每一个步骤的详细分析:

第一步:解析HTML,构建DOM树

过程详解

请先看图:

屏幕截图 2025-06-26 203838.png

当用户输入URL后,浏览器通过网络请求获取HTML文件(相关内容详解文章,点我打开链接)。

此时,HTML文件本质上是一段字节流,浏览器会根据HTTP头中的Content-Type(如text/html)和编码格式(如UTF-8)将其解码为可读的HTML字符串。

HTML解析器(HTML Parser)开始工作,将字符串解析为DOM树。解析过程中,浏览器会按以下步骤处理:

  1. 令牌化(Tokenization) :通过正则匹配标签名、属性、注释等内容,将HTML字符串拆分为一个个“令牌”(Token)。

    例如: <div id="box"></div> 会被拆分为 <div> 、id="box" 、</div> 三个令牌。
    
  2. 节点构建(Tree Construction) :根据令牌生成对应的DOM节点对象。

    每个节点包含类型(如div)、属性(如id: "box")和子节点,最终不断递归,形成一个树状结构,称为DOM树

  3. 阻塞行为:解析HTML时,若遇到<script>标签,浏览器会暂停解析,优先执行脚本(除非脚本添加了asyncdefer属性)。

关键点与优化

  • DOM树的高效性:DOM树以树形结构存储数据,支持快速查找和操作(如querySelector)。

  • 性能优化:减少HTML文件大小(压缩空白符、合并资源)、避免深层嵌套结构,可加速解析。

DOM节点内存结构

以HTML片段<div id="box"><p>Hello</p></div>为例,DOM树的存储形式如下:

{
  nodeType: 1,
  nodeName: "DIV",
  nodeValue: null,
  attributes: { id: "box" },
  childNodes: [
        { 
          nodeType: 1,
          nodeName: "P",
          nodeValue: null,
          attributes: {},
          childNodes: [
                { 
                  nodeType: 3,
                  nodeName: "#text",
                  nodeValue: "Hello"
                }
          ],
          parentNode: [指向div节点]
        }
  ],
  parentNode: null                        
}

第二步:解析CSS,构建CSSOM树

过程详解

请先看图

屏幕截图 2025-06-26 205203.png

浏览器在解析HTML时,会同时下载CSS文件(通过<link>标签)。CSS文件同样需要经过解析,生成CSSOM树(CSS Object Model Tree)。

解析过程如下:

  1. 下载与解码:浏览器根据Content-Type(如text/css)和编码格式(如UTF-8)解码CSS文本。

  2. 词法分析(Lexical Analysis) :将CSS文本拆分为令牌(如选择器、属性值)。

  3. 规则生成:将令牌转换为CSS规则对象(如div { color: red })。

  4. 构建CSSOM树:将规则按选择器优先级(如!important、ID选择器、类选择器)和继承关系组合成树状结构。

关键点与优化

  • CSSOM树的特性:样式规则存在继承和层叠(如color: red继承至子元素),最终通过优先级计算确定每个元素的样式。

  • 阻塞渲染:CSS解析会阻塞HTML解析和渲染(因为浏览器需要知道所有样式才能确定布局)。

  • 优化建议

    • 将关键CSS内联到HTML中,减少请求。
    • 使用media属性延迟加载非关键CSS(如<link rel="stylesheet" media="print">)。

CSSOM节点内存结构

以CSS片段div.box { color: red }为例,其内存存储如下:

// CSS规则对象
{
  selector: "div.box", // 选择器
  specificity: [0, 1, 1], // 优先级计算(ID:0, 类:1, 元素:1)
  declarations: {
    color: "red", // 样式属性
    ... // 其他属性
  },
  next: null // 链表指针,指向下一个规则
}

第三步:合成渲染树(Render Tree)

过程详解

同样先看图再说:

a496de9fb92aae11b689a4194199328f.png

DOM树和CSSOM树构建完成后,浏览器会将两者合并生成渲染树(Render Tree)。渲染树仅包含需要渲染的节点(如<div><span>),忽略不可见元素(如display: none的节点)。

合成过程如下:

  1. 匹配规则:将DOM节点与CSSOM规则匹配,确定每个节点的最终样式。

  2. 生成渲染节点:每个可见节点生成对应的渲染节点(Render Object),包含几何信息(如位置、大小)和样式属性。

  3. 层级关系:渲染树的结构与DOM树类似,但会根据z-indexposition等属性调整节点的层级。

关键点与优化

  • 渲染树的轻量化:渲染树仅包含可见元素,减少计算开销。
  • 性能影响:频繁修改DOM或CSSOM会导致渲染树频繁重建,触发重排(Reflow)。

第四步:布局(Layout)

过程详解

布局阶段(也称重排)的目标是计算每个元素在屏幕上的精确位置和大小。

浏览器会遍历渲染树,应用盒模型规则(如marginpaddingborder),并结合文档流(Normal Flow)和定位(如floatabsolute)确定元素的位置。

关键步骤:

  1. 根节点计算:从根节点(<html>)开始,计算视口大小。

  2. 递归布局:依次计算子元素的位置和大小。例如:

    div {
      width: 100px;
      height: 100px;
      margin: 10px;
    }
    

    浏览器会计算div的实际宽度为100px + 10px*2 = 120px

  3. BFC与IFC:通过块级格式化上下文(BFC)和行内格式化上下文(IFC)管理元素的排列规则。

关键点与优化

  • 重排的代价:布局是性能开销最大的阶段之一,频繁触发会导致页面卡顿。

  • 优化建议

    • 避免频繁读取布局属性(如offsetWidth),因为这会强制触发同步重排。
    • 使用transformopacity进行动画,避免修改布局属性(如leftwidth)。

第五步:分层(Layering)

过程详解

分层阶段的目的是将页面划分为多个独立的图层(Layer),每个图层可以独立渲染,提升性能。浏览器会根据以下条件创建新图层:

  1. 透明元素(如opacity < 1)。
  2. 3D变换(如transform: translateZ(0))。
  3. 固定/绝对定位(如position: fixed)。
  4. z-index非0的堆叠上下文

每个图层会生成一个独立的合成树(Composited Layer Tree),包含图层的层级关系和绘制顺序。

以下面浏览器的主页为例,在你刚打开浏览器的时候,你看到的页面可能和我类似,是这个样子:

屏幕截图 2025-06-27 160123.png

然而,如果你打开浏览器的控制台页面(快捷键f12 或 鼠标右键点击检查),使用图层(layers)工具:

image.png

随后,你会看到,你看到的页面并不是一个平面图,而是由一个个小区快,通过布局一层层叠在页面上,最后才组成你所看到的页面:

屏幕截图 2025-06-27 160142.png

关键点与优化

  • GPU加速:独立图层由GPU加速绘制,减少CPU负担。
  • 性能平衡:过多图层会增加内存消耗,需合理使用will-changetransform属性。

第六步:绘制(Painting)

过程详解

绘制阶段将每个图层的内容转换为像素点,生成位图(Bitmap)。浏览器会遍历图层,按以下步骤绘制:

  1. 绘制顺序:从后往前绘制(遵循z-index和堆叠上下文规则)。
  2. 像素操作:将样式属性(如background-colorborder-radius)转换为像素点。
  3. 光栅化:将矢量图形(如SVG)转换为位图,供GPU使用。

关键点与优化

  • 重绘的代价:绘制操作涉及大量像素计算,频繁触发会导致性能下降。

  • 优化建议

    • 使用硬件加速(如transform)减少重绘。
    • 避免使用复杂的CSS效果(如大量渐变或阴影)。

第七步:合成显示(Display)

过程详解

合成显示是最终的渲染阶段,浏览器将所有图层的位图合并,生成最终的屏幕图像。过程如下:

  1. 图层合并:浏览器的合成线程(Compositor Thread)将图层按z-index顺序合并。

  2. GPU渲染:合并后的位图通过GPU发送到显示器,按帧率(如60Hz)刷新屏幕。

  3. 防闪烁处理:通过双缓冲(Double Buffering) 技术,避免绘制过程中的画面撕裂。

关键点与优化

  • 帧率控制:确保每帧绘制时间不超过16ms(60Hz屏幕)。

  • 动画优化:使用requestAnimationFrame确保动画与屏幕刷新率同步。

隐藏关卡:回流(Reflow)与重绘(Repaint)

过程详解

  1. 回流/重排(Reflow)
  • DOM树或CSSOM树发生变化,导致元素的几何属性(如位置、大小)改变时,浏览器需重新计算布局(Layout),这一过程称为回流

    触发回流后,浏览器会重新进行布局分层绘制合成显示 等步骤。

  • 触发条件

    • 修改元素的几何属性(如widthheightmarginpadding)。
    • 添加/删除可见的DOM节点。
    • 调整窗口大小(resize)或滚动页面(scroll)。
    • 读取某些布局属性(如offsetWidthgetBoundingClientRect),强制触发同步回流。
  1. 重绘(Repaint)
  • 元素的样式属性(如颜色、背景、透明度)改变但不影响布局时,浏览器需重新绘制像素,这一过程称为重绘

    触发重绘后,则仅需重新执行绘制合成显示 步骤,但仍需依赖布局和分层的结果。

  • 触发条件

    • 修改非几何属性(如colorbackground-colorvisibility)。
    • 激活伪类(如:hover)。

本文为作者的理解,如果内容有误,欢迎各位读者在评论区指正。

如果觉得这篇文章对你有所帮助,不妨动动小手,点赞 + 收藏 一波!🌟

99db98bccf5c6a316b4efb1f323da9a0.gif