3

14 阅读11分钟

一、浏览器渲染流程

  1. HTML文档解析

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

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

    1. 当主线程解析到<link>标签时,如果外部CSS文件尚未下载解析完成,主线程不会等待,而是继续解析后续HTML。预解析线程下载和解析CSS
    2. CSS下载解析完成前,浏览器不会渲染页面,因为CSS会影响元素的样式,提前渲染再重排,会导致页面闪烁,影响用户体验
  2. 遇到 JS 浏览器暂停 HTML 解析

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

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

  1. 生成CSSOM树

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

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

  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. 层叠:按 “选择器权重→声明顺序→来源(用户代理样式 < 用户样式 < 内联样式)” 规则解决样式冲突;
    3. 计算最终样式:为每个 DOM 节点生成ComputedStyle(计算样式),存储所有属性的最终值。
  1. 性能关键点

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

  1. 布局

  1. 核心步骤

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

  1. 构建布局 :基于 布局树,过滤掉不可见节点(如<head>display: none的元素),保留可见节点

  2. 布局计算

    1. 基于布局树,从根节点开始,算每个元素的几何属性——包括元素的位置(left、top、right、bottom)、大小(width、height、padding、margin)、以及元素之间的关系。
    2. 遵循盒模型规则,结合视口大小、父节点布局约束完成计算;
  3. 布局是“流式布局”:浏览器会按照文档流的顺序,依次计算元素的位置,一旦计算完成,就会确定元素在页面中的最终位置(除非后续触发重排)。

当修改了节点的几何属性,如大小、位置,就需要重新计算布局,这个过程也叫做重排

获取节点的几何属性时,如 offsetWidth / getBoundingClientRect/clientWidth强制重排

  1. 分层

  • 主线程会使用一套复杂的策略对整个布局树中进行分层。
  • 将页面进行分层,之后某个层变化时,就可以单独更新这一个图层,从而避免了全页面的更新,提高效率。
  • 滚动条、堆叠上下文(z-index)、transformopacity 等样式都会或多或少的影响分层结果,也可以通过will-change属性更大程度的影响分层结果。

  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 仅触发此阶段)

前期阶段( HTML 解析 → 绘制): CPU 主场这些阶段涉及复杂的逻辑判断、递归计算和指令生成,属于串行、高逻辑密度的任务,CPU 是唯一高效执行者;若主线程被阻塞(如长时间 JS 执行),会直接导致渲染卡顿。

后期阶段(光栅化 → 合成): GPU 主场这两个阶段是并行 、像素级运算,GPU 天生擅长处理大规模并行计算,因此性能远优于 CPU;当元素形成独立图层时(如设置 will-change: transform/opacity,或天然的堆叠上下文),修改 transform(位移 / 缩放 / 旋转)和 opacity 仅触发合成阶段,不触发布局和重绘


三、重排、重绘

重排触发场景:

  • 修改几何属性:widthheightpaddingmarginleft/top

  • 增删/移动 DOM 节点:appendChildremoveChildinsertBefore

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

  • 修改类名或激活伪类:.class 变化、:hover

  • 窗口尺寸变化:resize 事件

  • 图片/字体加载完成(影响占位尺寸)

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

重绘触发场景:

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

  • 阴影相关:box-shadowtext-shadow

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

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

  • 轮廓相关:outline-coloroutline-style

  • 可见性:visibility(非 display

  • 光标:cursor

  • 滤镜:filter

  • 透明度:opacity

    • opacity 值小于1时,会创建新的层叠上下文,并可能触发图层创建
    • 修改 opacity 仅触发合成(最快),不触发重绘(如果独立图层)

四、渲染性能优化

渲染阶段优化目标核心优化手段
网络请求减少阻塞预解析、关键资源优化
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提示浏览器创建独立图层,提前做好优化准备
  1. 避免渲染阻塞

  1. JS 优化

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

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

script标签中defer和async的区别

如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。

其区别如下:

  • 无:遇到script标签时,浏览器暂停 HTML 解析,先同步下载 JS 文件;下载完成后立即执行 JS 代码,执行完毕后才恢复 HTML 解析,JS 下载 / 执行全程阻塞 HTML 解析,若 JS 文件体积大、下载慢,会导致页面长时间白屏,首屏渲染延迟;
  • defer:异步下载,等待 HTML 解析完成后,按脚本出现的顺序依次执行。不阻塞 HTML 解析。适用于有依赖关系的脚本。
  • async:异步下载(不阻塞 HTML 解析),下载完成后立即执行。如果此时 HTML 仍在解析,执行过程会暂停 HTML 解析。因此,async 脚本的执行可能阻塞渲染,且执行顺序不保证(谁先下载完谁先执行)。适用于独立脚本(如统计代码)。