浏览器渲染原理

浏览器渲染原理

浏览器渲染过程

HTML解析器:

解析 HTML 文档,将各个 HTML 元素逐个转化成 DOM 节点,从而生成 DOM 树。

CSS解析器:

将 CSS 转化为对象,将这些 CSS 对象组装起来,构建 CSSOM 树。

渲染树什么时候构建:

DOM 树和 CSSOM 树都构建完成以后,浏览器会根据这两颗树构建出一颗渲染树。

计算布局基于渲染树:

渲染树构建完毕之后,元素的位置关系以及需要应用的样式就确定了,这时浏览器会计算出所有元素的大小和绝对位置。

计算布局采用的是深度优先遍历递归算法

页面什么时候绘制:

页面布局完成之后,浏览器会将根据处理出来的结果,把每一个页面图层转换为像素,并对所有的媒体文件进行解码。

陈述 HTML 渲染成网页元素的核心流程:

浏览器渲染的核心流程是:解析HTML构建DOM树 → 解析CSS生成CSSOM树 → 合并成渲染树 → 计算布局(重排) → 分层绘制 → 合成显示,其中重排和重绘是性能关键点,优化时应优先减少布局计算和利用GPU加速合成。

  1. 解析 HTML ,构建 DOM 树
  • 字节流 → 字符流 → Tokens → DOM节点 → 树结构
    • 解码: 浏览器接收 HTML 原始字节(如 utf-8 编码),解码为字符。

    • 词性分析:HTML 解析器将字符流拆解为有意义的标记(Tokens),如<div>"text"

    • 语法分析:根据 HTML 语法规则,将 Tokens 转换为 Node 对象,构建 DOM 树(文档对象模型)

    • 阻塞点:

      • 遇到
  1. 解析 CSS,构建 CSSOM 树

    • 层叠与继承:解析所有 CSS 规则(外部、内联、行内样式),计算每个节点的最终样式。

      • 选择器匹配:从右向左匹配(如.box p 先找所有再过滤父元素)。

      • 阻塞渲染:CSSOM 未构建完成时,浏览器会延迟页面渲染(避免FOUC:用户可能会先看到无样式内容)。

  2. 合并 DOM 与 CSSOM ,生成渲染树(Render Tree)

    • 可见节点组合:渲染树仅包含需要显示的节点(排除 display:none、等)
    • 样式计算:为每个节点分配最终计算的样式值。
  3. 布局(Layout/Reflow)

    • 计算几何信息:遍历渲染树,计算每个节点的精确位置和大小(像素单位)。

      • 盒模型处理:根据 width、padding、margin 等属性确定布局。
      • 相对单位转换: 将 % 、vw 、rem 等转换为屏幕像素值。
      • 全局 vs 局部重排:修改几何属性(如宽度)可能触发从根节点开始的重新布局。
  4. 分层

    • 合成层优化:浏览器将页面划分为多个层,例如:

      • 显示触发:transform、opacity、will-change 等属性会创建独立层。
      • 隐式分层:overflow、z-index 可能导致分层。
    • 目的:实现 GPU 加速,独立绘制和合成。

  5. 绘制(print)

    • 生成绘制指令:将每个节点的外观(颜色、边框等)转换为绘制操作(如"填充矩形")。
    • 栅格化:将绘制指令转换为像素位图(通常在光栅线程完成)。
  6. 合成与显示

    • 合成器线程工作:将各层(包括位图和变换信息)合成为最终屏幕图像。
    • GPU加速:通过 GPU 处理层变换(如平移、缩放),直接合成不触发重绘。
    • 提交帧缓冲区:最终图像由 GPU 输出到显示器。

哪个环节容易造成性能问题

重排(Layout)和长任务(Long Tasks)最容易导致性能问题**,因为重排触发整个渲染树的重新计算,而长任务会阻塞主线程。优化关键是减少 DOM 操作、使用 transform/opacity 动画,并拆分耗时任务。比如用 requestAnimationFrame 替代 setTimeout 做动画,可以避免丢帧;用 Flexbox 布局比 Float 更高效,因为减少了重排计算。

  1. HTML/CSS 解析阻塞(关键渲染路径延迟)

    问题原因:

    • 同步加载的

计算布局的主要目的是什么

计算布局的目的是确定每个可见元素在屏幕上的精确位置和几何尺寸(如宽度、高度、边距等),确保页面内容按照 CSS 盒模型正确排列,为后续的绘制(Paint)和合成(Composite)提供基础。

DOM 树的构建

1. DOM 树的构建流程:

html字符串 分词器 token流 栈 DOM树

2. 从 html 字符串到DOM树的详细步骤

复制代码
2.1 示例 HTML 代码

<html>
<link rel="stylesheet" href='/a.css'>
<head>
    <title>DOM解析示例</title>
</head>
<body>
    <div class="container">
        <h1>Hello World</h1>
        <p>这是一个段落</p>
    </div>
    <script src='/a.js'></script>
</body>
</html>

HTML 字符串输入之后,分词器将 HTML 字符串拆解为有意义的Token序列:

Token类型 示例
开始标签 <html>, <div>
结束标签 </div>, </body>
属性 class="container"
文本内容 "Hello World", "这是一个段落"

生成的Token流(简化)

复制代码
 `<div>`
`class="header"`
`<h1>`
 `"标题"`
 `</h1>`
 `<p>`
 `"段落内容"`
 `</p>`
 `</div>`

栈结构处理Token流

浏览器使用栈结构管理节点层级关系:

  1. 遇到开始标签:创建元素节点并入栈

  2. 遇到结束标签:出栈并建立父子关系

  3. 遇到文本内容:创建文本节点挂载到当前栈顶元素

最终 DOM 树结构:

Document html link head body title DOM解析示例 div script h1 p Hello World 这是一个段落

遇到 < script> 时的处理:

  • 遇到 <script>(无 async/defer)时,暂停DOM解析,立即下载(如果是外部脚本)并执行脚本。
  • 原因 :JS 可能通过 document.write() 或 DOM API 修改HTML结构,浏览器必须保证执行时的DOM状态正确。
  • 优化方法: 使用 asyncdefer 异步加载脚本,避免阻塞解析。

遇到 CSS 时的处理

  • <link rel="stylesheet">

    • 异步下载(不阻塞DOM解析)。
    • 但会阻塞渲染树的合成(必须等待CSSOM构建完成)。
  • <style> 标签

    • 直接解析CSS规则,不影响DOM构建。

如果 < script>前有未加载完的CSS,JS 会立即执行吗

不会。浏览器必须等待前面的 CSS 加载并构建 CSSOM 后才会执行 <script>,因为 JS 可能依赖样式计算(如 getComputedStyle)。优化方法是 将非关键 JS 标记为 defer调整资源加载顺序 。现代浏览器会预加载扫描(Preload Scanner)提前下载资源,但执行顺序仍遵循依赖关系。可通过 <link rel="preload"> 进一步优化关键 CSS 的加载优先级。

  • CSS 阻塞 JS:是设计上的必要行为,确保样式计算正确性。

  • 优化关键

    • 内联首屏关键 CSS。
    • 非关键 JS 用 async/defer
    • 非关键 CSS 异步加载(如 preload)。

Tokens 流的作用

Tokens 流是浏览器将 HTML 字符串转换为结构化 DOM 树的中间产物,它明确标识节点类型、属性和层级关系,为 DOM 构造器提供标准化输入,同时支持 HTML 的容错解析。在Vue/React等框架中,虚拟DOM的diff算法本质上也是先生成类似Tokens的轻量描述,再映射到真实DOM,原理相通。

DOM 解析和 CSS 解析会相互阻塞吗

DOM 和 CSSOM 是并行执行的,不会产生影响,是独立的两个数据结构,浏览器在构建 DOM 的同时,如果样式也加载完成了,那么 CSSOM 树也会同步构建。

渲染树的构建

在 DOM 树和 CSSOM 树都解析完成之后,就会进入渲染树的构建阶段。

渲染树就是 DOM 树和 CSSOM 树的结合,会得到一个可以知道每个节点会应用什么样式的数据结构。

DOM 树可能包含一些不可见元素,不如 head 标签,使用 display:none 属性的元素等,所以在显示页面之前,还要额外地构建一颗只包含可见元素的渲染树。

渲染树是如何构建的

将 DOM 树和 CSSOM 树结合有一个映射关系,将 CSSOM 上面的选择器对应的样式映射到 DOM 树上面,然后在渲染树时把没用的节点或者不显示的节点从渲染树上剥离掉。

页面布局主要完成哪些工作

计算DOM节点的样式 应用盒模型计算节点的大小和位置 绘制大小和边框

绘制阶段的工作内容是什么

绘制阶段是浏览器渲染流水线中将渲染树(Render Tree)转换为屏幕像素 的过程,也称为 Rasterization(栅格化) 。其核心任务是将布局计算后的几何信息和样式转换为实际的像素点,最终显示在屏幕上。

经过Layout(布局)阶段后,每个渲染树节点已有确定的几何属性,比如位置(x,y坐标),尺寸(width,height),边框、边距等盒模型属性,浏览器为每个渲染树节点生成一组绘制指令,描述如何填充像素,将绘制指令转换为像素位图。

  1. 遍历渲染树对节点进行分层,形成独立的合成层(Layer树),目的是优化渲染性能

    .transform-layer {
    transform: translateZ(0); /* 强制提升为独立层 /
    will-change: transform; /
    提前提示浏览器优化 /
    opacity: 0.5; /
    透明动画需分层 /
    position: fixed; /
    固定定位 /
    filter: blur(5px); /
    滤镜效果 */
    }

  2. 合成线程会将每个图层分割为大小固定的图块(通常为256x256或512x512像素),目的是:

  • 并行栅格化:GPU 可同时处理多个图块,加快渲染速度。
  • 按需加载:只栅格化视口附近的图块(如滚动时动态加载)

Layer 分割为图块Tile1 分割为图块Tile2 ...

  1. 将绘制指令组合成一个绘制列表,指导光栅化线程如何填充像素

    复制代码
     // 示例绘制指令列表

    [
    { type: "fillRect", x: 0, y: 0, width: 100, height: 100, color: "#F00" },
    { type: "drawText", text: "Hello", x: 10, y: 20, font: "Arial" },
    { type: "drawImage", src: "logo.png", x: 50, y: 50 }
    ]

为什么需要分层:

  • 减少重绘范围:修改某一层时,只需重绘该层,不影响其它层
  • GPU 加速:独立层可由 GPU 直接处理(硬件加速),提升动画性能。

如何强制触发分层:

复制代码
使用 transform:translateZ(0) 或 will-change 属性,但需避免过度分层导致内存占用过高。

哪些操作会导致分层:

will-change:transform,opacity;position:fixed,absolute + z-index等操作

css动画在独立图层中进行,不会引起回流,是由 GPU 硬件加速完成的性能很高

绘制完成后如何显示网页

按照绘制列表为每个图层的分块生成图片 将不同图层分块的图片合成一个分块 根据优先显示级别将图片输送到显卡的缓冲区中 GPU渲染网页

重绘(Repaint)

  • 定义:浏览器重新绘制元素的外观(颜色、边框等),不改变布局
  • 触发:修改不影响布局的样式(如color、visibility、opacity、background,修改背景色、文字颜色、边框颜色、样式等 )
  • 第一次渲染内容称之为绘制(paint),之后重新渲染称之为重绘。重绘不会带来重新布局,所以并不一定伴随重排。

回流/重排(reflow)

  • 定义:浏览器重新计算元素的几何属性(位置、大小),更新渲染树布局。

  • 触发:修改影响布局的 CSS 属性(如width、padding、top、position)或 DOM 结构

  • 第一次确定节点的位置和大小称之为布局(Layout),之后对节点的位置、大小修改重新计算称之为回来/重排。回流/重排必然导致重绘,比如改变一个网页元素的位置,就会同时触发重排和重绘,因为布局改变了,所以回流是一件很消耗性能的事情。

常见的触发回流/重排操作

  • Reflow 的成本比 Repaint 的成本高得多的多。

  • 所以在开发中要尽量避免发生回流:

    1. 修改样式时尽量一次性修改

      • 将多次改变样式属性的操作合并成一次操作
      • 预先定义好 class,然后修改 DOM 的 className
    2. 尽量避免频繁的操作 DOM

      • 我们可以在一个 DocumentFragment 或者父元素中将要操作的 DOM 操作完成,再一次性的操作;
    3. 尽量避免通过 getComputedStyle 获取尺寸、位置等信息;

    4. 对某些元素使用 positionabsolute 或者 fixed

      • 并不是不会引起回流,而是开销相对较小,不会对其他元素造成影响。
相关推荐
然我3 分钟前
react-router-dom 完全指南:从零实现动态路由与嵌套布局
前端·react.js·面试
一_个前端12 分钟前
Vite项目中SVG同步转换成Image对象
前端
202612 分钟前
12. npm version方法总结
前端·javascript·vue.js
用户876128290737413 分钟前
mapboxgl中对popup弹窗添加事件
前端·vue.js
帅夫帅夫14 分钟前
JavaScript继承探秘:从原型链到ES6 Class
前端·javascript
a别念m14 分钟前
HTML5 离线存储
前端·html·html5
goldenocean1 小时前
React之旅-06 Ref
前端·react.js·前端框架
子林super1 小时前
【非标】es屏蔽中心扩容协调节点
前端
前端拿破轮1 小时前
刷了这么久LeetCode了,挑战一道hard。。。
前端·javascript·面试
代码小学僧1 小时前
「双端 + 响应式」企业官网开发经验分享
前端·css·响应式设计