浏览器渲染zz

26 阅读11分钟

一、浏览器渲染流程

渲染流程,本质就是 HTML 字符串转换为屏幕上像素点的过程。这个过程看似简单,实则是一套由多进程 多线程协同完成的复杂流水线。

  1. HTML文档解析

HTML解析是渲染的第一步,这一步的输入是通过网络请求拿到的 HTML 字符串。

  1. 核心解析阶段

  1. 生成DOM树

解析的过程中遇到HTML元素,会解析HTML元素最终生成DOM树

  1. 生成CSSOM树

解析的过程中遇到style标签link元素内联样式CSS样式,会解析CSS生成CSSOM树

第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、内联样式均会包含在 CSSOM 树中。

  1. 外部资源处理

为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。

  1. 遇到 CSS 暂停渲染(不暂停 HTML 解析)

    1. 当主线程解析到<link>标签时,如果外部CSS文件尚未下载解析完成,主线程不会等待,而是继续解析后续HTML。预解析线程下载和解析CSS
    2. CSS下载解析完成前,浏览器不会渲染页面,因为浏览器需要完整的CSSOM才能构建布局树,所以首次渲染必须等待CSS加载完成
  2. 遇到 JS 浏览器暂停 HTML 解析

    1. 如果主线程解析到script位置,会停止解析HTML,转而等待JS文件下载好,并将全局代码解析执行完成后,才能继续解析HTML
    2. JS代码的执行过程可能会修改当前的DOM树,所以DOM树的生成必须暂停。这就是JS会阻塞HTML解析的根本原因。这也是为什么建议将<script>标签放在<body>底部的原因之一。
    3. <script> 标签(无 defer/async)会阻塞 HTML 解析。而 deferasync 可以优化这一行为。
  3. script 标签 中defer和async的区别

    1.   如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。
    2.   其区别如下:
    3. 无:遇到script标签时,浏览器暂停 HTML 解析,先同步下载 JS 文件;下载完成后立即执行 JS 代码,执行完毕后才恢复 HTML 解析,JS 下载 / 执行全程阻塞 HTML 解析,若 JS 文件体积大、下载慢,会导致页面长时间白屏,首屏渲染延迟。
    4. defer:异步下载,等待 HTML 解析完成后,按脚本出现的顺序依次执行。不阻塞 HTML 解析。适用于有依赖关系的脚本。
    5. async:异步下载(不阻塞 HTML 解析),下载完成后立即执行。如果此时 HTML 仍在解析,执行过程会暂停 HTML 解析。因此,async 脚本的执行可能阻塞渲染,且执行顺序不保证(谁先下载完谁先执行)。适用于独立脚本(如统计代码)。
  1. 样式计算

  1. 核心步骤

  1. CSS 结构化:将外部样式表(<link>)、内部样式表(<style>)、内联样式(style属性)浏览器默认样式转换为浏览器可理解的StyleSheet结构;

  2. 属性标准化:将 CSS 属性值转换为标准化格式(如2em → 32pxblue → rgb(0,0,255)bold → 700);

  3. 样式 继承 与层叠

    1. 继承:子节点继承父节点的可继承属性(如font-sizecolor)(不可继承属性(如border/padding/width)需显式声明,继承属性的优先级低于显式声明,层叠时需注意)
    2. 层叠:按 “选择器权重→声明顺序→来源(用户代理样式 < 用户样式 < 内联样式)” 规则解决样式冲突;
  4. 计算最终样式:为每个 DOM 节点生成ComputedStyle(计算样式),存储所有属性的最终值。

  1. 性能关键点

  • 复杂选择器(如div > ul li a)会增加样式计算耗时,建议简化选择器(如使用类选择器.link);
  • CSS 规则匹配是 “从右到左” 的(如.container .item先匹配.item再匹配.container),避免深层嵌套。

  1. 布局

依次遍历 DOM 树的每个节点,计算节点的几何信息,比如节点的宽高、包含块的位置等等,这步完成后,最终会得到一棵布局树。

DOM 树和 Layout 树不一定是一一对应的,因为布局树展示的是元素的几何信息,而有些元素因为各种各样的 CSS 样式,最终导致页面的呈现和 DOM 结构不一致。比如:有的元素设置了display: none,就不会在页面上有几何信息。

  1. 分层

  • 主线程会使用一套复杂的策略对整个布局树中进行分层。每个图层对应一个独立的渲染上下文,后续绘制、光栅化、合成都是按图层进行的。
  • 将页面进行分层,之后某个层变化时,就可以单独更新这一个图层,从而避免了全页面的更新,提高效率。
  • 滚动条、堆叠上下文(z-index)、transformopacity 等样式都会或多或少的影响分层结果,可通过 will-change: transform/opacity 等属性主动提示浏览器为元素创建独立图层,提前优化分层策略

  1. 绘制

绘制阶段为每个图层生成绘制列表,定义 “先画什么、后画什么”(如先画背景,再画边框,最后画文本)

  1. 核心流程

    1. 渲染引擎将图层的绘制过程拆解为原子化指令(如 “绘制矩形”“绘制文本”“绘制渐变”);
    2. 按绘制顺序组合指令生成绘制列表
  1. 关键特性

    1. 核心绘制顺序:背景色 → 背景图 → 边框 → 文本 / 替换元素 → 子元素;
    2. 关键原则:后绘制的内容会覆盖先绘制的,子元素默认在父元素文本之上绘制。
    3. 绘制是 分层 :不同层可以独立绘制
  1. 性能关键点

  • 绘制指令越复杂(如多层阴影、渐变),绘制耗时越长;
  • 避免给大尺寸图层添加复杂绘制属性(如box-shadow)。

  1. 光栅化

光栅化是将矢量图形(绘制指令)转换为位图(像素矩阵)的过程。

  1. 核心流程

    1. 接收绘制列表:合成线程从主线程接收每个图层的绘制列表
    2. 分块: 将大图层切割成小块(通常是256x256或512x512像素的图块)
    3. 优先级排序 优先光栅化视口内的图块(用户当前可见区域)
    4. 执行光栅化: 将每个图块的绘制指令转换为 位图 (实际像素)
    5. 存储 位图 将生成的位图存储在GPU 内存(或CPU内存)中,供合成使用

  1. 合成

合成是将光栅化后生成的一块块位图,按照正确的层叠顺序合并成最终画面,显示在屏幕上的过程。

合成是完全在 GPU 进程中由合成线程完成的,不依赖主线程,因此即使主线程卡顿,动画仍可流畅运行。

  1. 核心步骤

    1. 接收 位图 : 合成线程从 GPU 显存中获取已完成光栅化的图块位图
    2. 计算变换: 根据图层的transform、opacity等属性,计算每个图块需要应用的变换矩阵
    3. 绘制图块到合成层: 将每个图块的位图绘制到对应的合成层(按正确位置和变换)
    4. 合并合成层: 按照层叠顺序(z-index、堆叠上下文)将所有合成层合并成一张最终图像
    5. 提交显示: 将最终图像提交给GPU的显示缓冲区,等待屏幕刷新显示
  1. 性能关键点

  1. 优先使用「仅触发合成」的属性,使用 transform (位移/缩放/旋转) 和 opacity 。它们只影响合成, GPU 处理极快。

二、CPU和GPU

  • CPU(主线程):主导逻辑运算、DOM 操作与布局计算。任务繁重且串行执行,高负载下易引发主线程阻塞,导致页面卡顿。
  • GPU(合成线程):专攻图形渲染、位移、旋转与缩放。利用硬件加速并行处理,高效丝滑,且不占用主线程资源
  1. CPU vs GPU 本质区别

对比维度CPUGPU
核心数量4-16 个高性能核心数千个简单核心
并行能力同时处理几个任务同时处理几千个任务
设计目标复杂逻辑控制大规模并行计算
适合任务串行、分支预测矩阵、图像、并行
  1. 渲染流程中的分工

CPU 负责"怎么画"的复杂决策,GPU 负责"快点画"的并行执行,两者配合实现流畅的渲染。

渲染阶段执行者核心原因
HTML 解析CPU复杂文本解析、树结构(DOM)构建
CSS 解析CPU样式规则解析、选择器匹配、构建 CSSOM
样式计算CPU样式继承、层叠规则、属性值标准化计算
布局 (Layout)CPU复杂几何计算(宽 / 高 / 坐标)、元素依赖关系处理
分层CPU分层策略判断、堆叠上下文分析
绘制 (Paint)CPU生成原子化绘制指令(不直接生成像素)
光栅化GPU 优先(CPU)并行像素填充,将指令转换为位图(GPU 并行效率远高于 CPU),小尺寸图层 / 低端设备 / GPU 内存不足时,浏览器会回退到 CPU 光栅化
合成GPU并行图层合并、变换矩阵运算(transform/opacity 仅触发此阶段)
  1. 前期阶段( HTML 解析 → 绘制): CPU 主场这些阶段涉及复杂的逻辑判断、递归计算和指令生成,属于串行、高逻辑密度的任务,CPU 是唯一高效执行者;若主线程被阻塞(如长时间 JS 执行),会直接导致渲染卡顿。

  2. 后期阶段(光栅化 → 合成): GPU 主场这两个阶段是并行 、像素级运算,GPU 天生擅长处理大规模并行计算,因此性能远优于 CPU;

    1. 光栅化优先由 GPU 完成,但若GPU内存不足或图层过小,浏览器可能回退到CPU光栅化。
    2. 当元素形成独立图层时(如设置 will-change: transform/opacity,或天然的堆叠上下文),修改 transform(位移 / 缩放 / 旋转)和 opacity 仅触发合成阶段,不触发布局和重绘

三、重排、重绘

重排触发场景:

  • 修改几何属性:widthheightpaddingmarginleft/top

  • 增删/移动 DOM 节点:appendChildremoveChildinsertBefore

  • 修改内容:innerHTML、文本节点变化

  • 窗口尺寸变化:resize 事件

  • 强制重排:在样式修改后读取布局属性(如 offsetTopclientWidth

重绘触发场景:

  • 颜色相关:colorbackground-colorborder-color(不修改宽度时)

  • 阴影相关:box-shadowtext-shadow

  • 背景相关:background-imagebackground-positionbackground-size

  • 文本样式:text-decorationfont-style(不影响字体大小时)

  • 轮廓相关:outline-coloroutline-style

  • 可见性:visibility(非 display

重排必然触发重绘,重绘不一定触发重排

四、渲染性能优化

渲染阶段优化目标核心优化手段
网络请求减少阻塞预解析、关键资源优化
HTML解析减少阻塞精简HTML、JS异步
CSS解析减少阻塞关键CSS内联、避免@import
样式计算减少耗时简化选择器、避免通配符
布局避免重排批量操作、离线DOM
分层合理分层will-change、独立图层
绘制减少重绘优先使用 transform/opacity(仅触发合成)、避免大面积重绘区域
光栅化加速光栅化视口优先、GPU加速
合成提升合成效率transform/opacity、图层管理
  1. 减少重排/重绘

  • 批量修改 DOM 和样式(如使用 DocumentFragment 批量添加节点,或先隐藏节点 display: none,修改完成后再显示)。
  • 避免频繁读取布局属性(如 offsetWidth、clientHeight、getBoundingClientRect()),若需多次读取,可缓存结果。
  • 使用 transform 和 opacity 实现动画(仅触发合成,不触发布局和重绘),替代修改 width、height、top 等几何属性。
  1. 利用GPU加速

  1. 动画用 transform opacity

    1. transform(如translatescale)和opacity的修改仅触发合成,不触发重排和重绘,是性能最优的动画实现方式
  2. 创建独立 图层

    1. 对频繁动画的元素,用will-change提示浏览器创建独立图层,提前做好优化准备,但不要滥用will-change,否则会占用过多GPU内存。
  1. 避免渲染阻塞

  1. JS 优化

    1. 首屏非必需的 JS 用async/defer加载;
    2. 大型 JS 文件用代码分割(Code Splitting),按需加载;
  2. CSS 优化

    1. 外部 CSS 文件放在<head>中(确保样式优先加载);
    2. 非首屏 CSS 用媒体查询media="print"等,不阻塞首屏渲染: