一、浏览器渲染流程
HTML文档解析
-
为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程 ,率先下载
HTML中的外部CSS文件和 外部的JS文件。 -
遇到
CSS暂停渲染(不暂停 HTML 解析),下载 + 解析 CSS- 当主线程解析到
<link>标签时,如果外部CSS文件尚未下载解析完成,主线程不会等待,而是继续解析后续HTML。 - 下载CSS的工作由预解析线程负责
- 主线程和预解析线程并行工作
- 但会阻塞布局 树 构建(必须等待CSSOM完成)
- CSSOM构建完成之前,JS执行会被阻塞(因为JS可能会查询元素的样式,如果CSSOM没好,查到的样式是不完整的,为了防止"读到旧样式")
- 遇到
JS浏览器暂停 HTML 解析,确保 JS 执行时 DOM 树状态完整。外部 JS 并非 "阻塞下载"(预解析线程会异步下载外部 JS),而是阻塞 HTML 解析和 DOM 构建 (下载完成后执行 JS 时,主线程暂停解析);内联 JS 无下载过程,直接阻塞 HTML 解析(async/defer可改变外部 JS 的阻塞行为)。
- 当主线程解析到
生成DOM树
解析的过程中遇到HTML元素会解析HTML元素最终生成DOM树;

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

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

样式计算
核心步骤
-
CSS 结构化 :将外部样式表(
<link>)、内部样式表(<style>)、内联样式(style属性)转换为浏览器可理解的StyleSheet结构; -
属性标准化 :将 CSS 属性值转换为标准化格式(如
2em → 32px、blue → rgb(0,0,255)、bold → 700); -
样式 继承 与层叠:
- 继承:子节点继承父节点的可继承属性(如
font-size、color)(不可继承属性(如border/padding/width)需显式声明,继承属性的优先级低于显式声明,层叠时需注意) - 层叠:按 "选择器权重→声明顺序→来源(用户代理样式 < 用户样式 < 内联样式)" 规则解决样式冲突;
- 计算最终样式 :为每个 DOM 节点生成
ComputedStyle(计算样式),存储所有属性的最终值。
- 继承:子节点继承父节点的可继承属性(如
性能关键点
-
复杂选择器(如
div > ul li a)会增加样式计算耗时,建议简化选择器(如使用类选择器.link); -
CSS 规则匹配是 "从右到左" 的(如
.container .item先匹配.item再匹配.container),避免通配符*和深层嵌套。- 从右到左匹配的核心原因:减少匹配次数(先定位所有
.item,再筛选其父级是否为.container,而非遍历所有.container再找子级.item)
- 从右到左匹配的核心原因:减少匹配次数(先定位所有

布局
核心步骤
-
构建布局 树 :基于 DOM 树和 CSSOM 树合并生成的渲染树,过滤掉不可见节点(如
<head>、display: none的元素),保留可见节点 -
布局计算:
- 基于布局树,从根节点开始,算每个元素的几何属性------包括元素的位置(left、top、right、bottom)、大小(width、height、padding、margin)、以及元素之间的关系。
- 遵循盒模型规则,结合视口大小、父节点布局约束完成计算;
-
布局树仅包含可见节点的几何布局信息,与 DOM 树结构不完全一致(如
display: none节点被剔除)。布局是"自上而下"的:从根节点开始,依次计算每个子节点的几何属性,因为父元素的大小和位置会影响子元素。 -
布局是"流式布局":浏览器会按照文档流的顺序,依次计算元素的位置,一旦计算完成,就会确定元素在页面中的最终位置(除非后续触发重排)。

关键特性
- 重排(Reflow) :布局计算是递归的,子节点布局变化会触发父节点重新计算,开销极大;
- 布局抖动(Layout Thrashing) :频繁读取 + 修改布局属性(如
offsetTop+style.top)会强制浏览器反复计算布局,导致性能暴跌(使用requestAnimationFrame批量操作,或先读取所有布局属性缓存,再批量修改,避免'读 - 改 - 读 - 改'的循环触发多次重排)

当修改了节点的几何属性,如大小、位置,就需要重新计算布局,这个过程也叫做重排
获取节点的几何属性时,如 offsetWidth / getBoundingClientRect/clientWidth 会强制重排
分层
- 主线程会使用一套复杂的策略对整个布局树中进行分层。
- 将页面进行分层,之后某个层变化时,就可以单独更新 这一个图层,从而避免了全页面的更新,提高效率。
- 分层不仅取决于
transform、opacity,还取决于video、canvas、iframe、will-change以及复杂的堆叠上下文(z-index) - 避免 过度创建合成层 的问题,每个图层需占用 GPU 显存,图层过多(如数百个独立图层)会导致显存不足,反而触发 GPU 卡顿,因此
will-change需谨慎使用(仅给高频动画元素声明)。

绘制
绘制阶段为每个图层生成绘制列表,定义 "先画什么、后画什么"(如先画背景,再画边框,最后画文本),
核心流程
- 渲染引擎将图层的绘制过程拆解为原子化指令(如 "绘制矩形""绘制文本""绘制渐变");
- 按绘制顺序组合指令生成绘制列表(Paint List);
关键特性
- 核心绘制顺序:背景色 → 背景图 → 边框 → 文本 / 替换元素 → 子元素;
- 子元素绘制规则:
z-index优先(数值大的后画),无z-index按 DOM 顺序(后写的后画) - 关键原则:后绘制的内容会覆盖先绘制的,子元素默认在父元素文本之上绘制。
- 绘制是 分层 的:不同层可以独立绘制
性能关键点
- 绘制指令越复杂(如多层阴影、渐变),绘制耗时越长;
- 避免给大尺寸图层添加复杂绘制属性(如
box-shadow)。

光栅化
上面我们已经获得了文档结构、元素的样式、元素的几何关系、绘画顺序,接下来把这些信息转化为显示器中的像素才能显示,这个转化的过程,就叫做光栅化。
核心流程
- 接收绘制列表:合成线程从主线程接收每个图层的绘制列表
- 分块: 将大图层切割成小块(通常是256x256或512x512像素的图块)
- 优先级排序 : 优先光栅化视口内的图块(用户当前可见区域)
- 执行光栅化: 将每个图块的绘制指令转换为 位图 (实际像素)
- 存储 位图 : 将生成的位图存储在GPU 内存(或CPU内存)中,供合成使用


合成
合成是将光栅化后生成的一块块位图,按照正确的层叠顺序合并成最终画面,显示在屏幕上的过程。
核心步骤
- 接收 位图 : 合成线程从 GPU 显存中获取已完成光栅化的图块位图
- 计算变换: 根据图层的transform、opacity等属性,计算每个图块需要应用的变换矩阵
- 绘制图块到合成层: 将每个图块的位图绘制到对应的合成层(按正确位置和变换)
- 合并合成层: 按照层叠顺序(z-index、堆叠上下文)将所有合成层合并成一张最终图像
- 提交显示: 将最终图像提交给GPU的显示缓冲区,等待屏幕刷新显示
性能关键点
- 优先使用「仅触发合成」的属性,使用
transform(位移/缩放/旋转) 和opacity。它们只影响合成,GPU 处理极快。 - 避免过度分层,如果图层太多、太大(显存爆炸),或者图层间依赖关系太复杂(导致无法并行合成),GPU 也会忙不过来,导致掉帧。
二、CPU和GPU
- CPU(主线程):主导逻辑运算、DOM 操作与布局计算。任务繁重且串行执行,高负载下易引发主线程阻塞,导致页面卡顿。
- GPU(合成线程):专攻图形渲染、位移、旋转与缩放。利用硬件加速并行处理,高效丝滑,且不占用主线程资源
CPU vs GPU 本质区别
| 对比维度 | CPU | GPU |
|---|---|---|
| 核心数量 | 4-16 个高性能核心 | 数千个简单核心 |
| 并行能力 | 同时处理几个任务 | 同时处理几千个任务 |
| 设计目标 | 复杂逻辑控制 | 大规模并行计算 |
| 适合任务 | 串行、分支预测 | 矩阵、图像、并行 |
渲染流程中的分工
CPU 负责"怎么画"的复杂决策,GPU 负责"快点画"的并行执行,两者配合实现流畅的渲染。
| 渲染阶段 | 执行者 | 核心原因 |
|---|---|---|
| HTML 解析 | CPU | 复杂文本解析、树结构(DOM)构建 |
| CSS 解析 | CPU | 样式规则解析、选择器匹配、构建 CSSOM |
| 样式计算 | CPU | 样式继承、层叠规则、属性值标准化计算 |
| 布局 (Layout) | CPU | 复杂几何计算(宽 / 高 / 坐标)、元素依赖关系处理 |
| 分层 | CPU | 分层策略判断、堆叠上下文分析 |
| 绘制 (Paint) | CPU | 生成原子化绘制指令(不直接生成像素) |
| 光栅化 | GPU 优先(或 CPU) | 并行像素填充,将指令转换为位图(GPU 并行效率远高于 CPU) |
| 合成 | GPU | 并行图层合并、变换矩阵运算(transform/opacity 仅触发此阶段) |
前期阶段( HTML 解析 → 绘制): CPU 主场 这些阶段涉及复杂的逻辑判断、递归计算和指令生成,属于串行、高逻辑密度的任务,CPU 是唯一高效执行者;若主线程被阻塞(如长时间 JS 执行),会直接导致渲染卡顿。
后期阶段(光栅化 → 合成): GPU 主场 这两个阶段是并行 、像素级运算 ,GPU 天生擅长处理大规模并行计算,因此性能远优于 CPU;修改 transform/opacity 时,浏览器可跳过前期阶段,仅触发 GPU 合成,是前端性能优化的方案。
三、重排、重绘与合成
页面交互过程中,JS/CSS 修改会触发渲染流水线的局部更新,按开销从高到低分为三类:
| 类型 | 触发条件 | 涉及渲染阶段 | 性能开销 | 优化优先级 |
|---|---|---|---|---|
| 重排(Reflow) | 几何属性变化:宽高、位置、display、DOM 增删 | 布局→分层→绘制→光栅化→合成 | 极高 | 最高 |
| 重绘(Repaint) | 绘制属性变化:颜色、背景、阴影、边框色 | 绘制→光栅化→合成 | 中等 | 中 |
| 合成(Composite) | 合成属性变化:transform、opacity | 仅合成阶段 | 极低 | 低(优先用) |
重排触发场景
- 修改几何属性:
width: 200px、left: 10px、margin: 8px; - 增删 / 移动 DOM 节点:
appendChild、removeChild、insertBefore; - 窗口操作:
resize、普通scroll仅触发合成(无重排),只有scroll时触发了元素几何位置变化(如固定定位元素跟随滚动)才会重排; - 读取布局属性:
offsetTop、clientWidth、getComputedStyle(强制浏览器提前完成重排)。
重绘触发场景
- 修改颜色属性:
color: red、background-color: #000; - 修改边框 / 阴影:
border-color: blue、box-shadow: 0 0 10px #000; - 修改文本样式:
text-shadow: 1px 1px 2px #333; - 修改背景:
background-image: url(new.png)、background-position: center。
合成触发场景
transform:translate/scale/rotate/skew;opacity:opacity: 0.5;will-change:提前声明元素即将变化的属性。
四、渲染性能优化
| 渲染阶段 | 优化目标 | 核心优化手段 |
|---|---|---|
| 网络请求 | 减少阻塞 | 预解析、关键资源优化 |
| HTML解析 | 减少阻塞 | 精简HTML、JS异步 |
| CSS解析 | 减少阻塞 | 关键CSS内联、避免@import |
| 样式计算 | 减少耗时 | 简化选择器、避免通配符 |
| 布局 | 避免重排 | 批量操作、离线DOM |
| 分层 | 合理分层 | will-change、独立图层 |
| 绘制 | 减少重绘 | 仅合成属性、避免大面积重绘 |
| 光栅化 | 加速光栅化 | 视口优先、GPU加速 |
| 合成 | 提升合成效率 | transform/opacity、图层管理 |
减少重排/重绘
- 批量修改 DOM 和样式(如使用 DocumentFragment 批量添加节点,或先隐藏节点 display: none,修改完成后再显示)。
- 避免频繁读取布局属性(如 offsetWidth、clientHeight、getBoundingClientRect()),若需多次读取,可缓存结果。
- 使用 transform 和 opacity 实现动画(仅触发合成,不触发布局和重绘),替代修改 width、height、top 等几何属性。
- 避免 表格布局:表格布局的重排成本极高(一个单元格变化会导致整个表格重新布局),优先用 Flex/Grid 布局。
利用GPU加速
-
动画用
transform和opacity:transform(如translate、scale)和opacity的修改仅触发合成,不触发重排和重绘,是性能最优的动画实现方式:
-
创建独立 图层:
- 对频繁动画的元素,用
will-change提示浏览器创建独立图层,提前做好优化准备
- 对频繁动画的元素,用
避免渲染阻塞
-
JS 优化:
- 首屏非必需的 JS 用
async/defer加载; - 大型 JS 文件用代码分割(Code Splitting),按需加载;
- 首屏非必需的 JS 用
-
CSS 优化:
- 外部 CSS 文件放在
<head>中(确保样式优先加载); - 非首屏 CSS 用媒体查询
media="print"等,不阻塞首屏渲染:
- 外部 CSS 文件放在
script标签中defer和async的区别
如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。
其区别如下:
- 无:遇到script标签时,浏览器暂停 HTML 解析,先同步下载 JS 文件;下载完成后立即执行 JS 代码,执行完毕后才恢复 HTML 解析,JS 下载 / 执行全程阻塞 HTML 解析,若 JS 文件体积大、下载慢,会导致页面长时间白屏,首屏渲染延迟;
defer:遇到script标签时,浏览器开始异步下载,HTML页面解析完才执行JS文件。立即下载,但延迟执行(整个页面都解析完毕之后再执行,不阻塞)。多个带async属性的标签,不能保证加载的顺序;async:遇到script标签时,浏览器开始异步下载,下载完成后如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析(可能会阻塞)。多个带defer属性的标签,按照加载顺序执行;