一、浏览器渲染流程
渲染流程,本质就是将 HTML 字符串转换为屏幕上像素点的过程。这个过程看似简单,实则是一套由多进程 多线程协同完成的复杂流水线。
-
HTML文档解析
HTML解析是渲染的第一步,这一步的输入是通过网络请求拿到的 HTML 字符串。
-
核心解析阶段
-
生成DOM树
解析的过程中遇到HTML元素,会解析HTML元素最终生成DOM树;
-
生成CSSOM树
解析的过程中遇到style标签、link元素、内联样式等CSS样式,会解析CSS生成CSSOM树。
第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、内联样式均会包含在 CSSOM 树中。
-
外部资源处理
为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。
-
遇到
CSS暂停渲染(不暂停 HTML 解析)- 当主线程解析到
<link>标签时,如果外部CSS文件尚未下载解析完成,主线程不会等待,而是继续解析后续HTML。预解析线程下载和解析CSS - CSS下载解析完成前,浏览器不会渲染页面,因为浏览器需要完整的CSSOM才能构建布局树,所以首次渲染必须等待CSS加载完成
- 当主线程解析到
-
遇到
JS浏览器暂停 HTML 解析- 如果主线程解析到script位置,会停止解析HTML,转而等待JS文件下载好,并将全局代码解析执行完成后,才能继续解析HTML
- JS代码的执行过程可能会修改当前的DOM树,所以DOM树的生成必须暂停。这就是JS会阻塞HTML解析的根本原因。这也是为什么建议将
<script>标签放在<body>底部的原因之一。 <script>标签(无 defer/async)会阻塞 HTML 解析。而defer和async可以优化这一行为。
-
script 标签 中defer和async的区别
- 如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。
- 其区别如下:
- 无:遇到script标签时,浏览器暂停 HTML 解析,先同步下载 JS 文件;下载完成后立即执行 JS 代码,执行完毕后才恢复 HTML 解析,JS 下载 / 执行全程阻塞 HTML 解析,若 JS 文件体积大、下载慢,会导致页面长时间白屏,首屏渲染延迟。
defer:异步下载,等待 HTML 解析完成后,按脚本出现的顺序依次执行。不阻塞 HTML 解析。适用于有依赖关系的脚本。async:异步下载(不阻塞 HTML 解析),下载完成后立即执行。如果此时 HTML 仍在解析,执行过程会暂停 HTML 解析。因此,async脚本的执行可能阻塞渲染,且执行顺序不保证(谁先下载完谁先执行)。适用于独立脚本(如统计代码)。
-
样式计算
-
核心步骤
-
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),避免深层嵌套。
-
布局
依次遍历 DOM 树的每个节点,计算节点的几何信息,比如节点的宽高、包含块的位置等等,这步完成后,最终会得到一棵布局树。
DOM 树和 Layout 树不一定是一一对应的,因为布局树展示的是元素的几何信息,而有些元素因为各种各样的 CSS 样式,最终导致页面的呈现和 DOM 结构不一致。比如:有的元素设置了display: none,就不会在页面上有几何信息。
-
分层
- 主线程会使用一套复杂的策略对整个布局树中进行分层。每个图层对应一个独立的渲染上下文,后续绘制、光栅化、合成都是按图层进行的。
- 将页面进行分层,之后某个层变化时,就可以单独更新这一个图层,从而避免了全页面的更新,提高效率。
- 滚动条、堆叠上下文(z-index)、
transform、opacity等样式都会或多或少的影响分层结果,可通过 will-change: transform/opacity 等属性主动提示浏览器为元素创建独立图层,提前优化分层策略
-
绘制
绘制阶段为每个图层生成绘制列表,定义 “先画什么、后画什么”(如先画背景,再画边框,最后画文本)
-
核心流程
- 渲染引擎将图层的绘制过程拆解为原子化指令(如 “绘制矩形”“绘制文本”“绘制渐变”);
- 按绘制顺序组合指令生成绘制列表;
-
关键特性
- 核心绘制顺序:背景色 → 背景图 → 边框 → 文本 / 替换元素 → 子元素;
- 关键原则:后绘制的内容会覆盖先绘制的,子元素默认在父元素文本之上绘制。
- 绘制是 分层 的:不同层可以独立绘制
-
性能关键点
- 绘制指令越复杂(如多层阴影、渐变),绘制耗时越长;
- 避免给大尺寸图层添加复杂绘制属性(如
box-shadow)。
-
光栅化
光栅化是将矢量图形(绘制指令)转换为位图(像素矩阵)的过程。
-
核心流程
- 接收绘制列表:合成线程从主线程接收每个图层的绘制列表
- 分块: 将大图层切割成小块(通常是256x256或512x512像素的图块)
- 优先级排序 : 优先光栅化视口内的图块(用户当前可见区域)
- 执行光栅化: 将每个图块的绘制指令转换为 位图 (实际像素)
- 存储 位图 : 将生成的位图存储在GPU 内存(或CPU内存)中,供合成使用
-
合成
合成是将光栅化后生成的一块块位图,按照正确的层叠顺序合并成最终画面,显示在屏幕上的过程。
合成是完全在 GPU 进程中由合成线程完成的,不依赖主线程,因此即使主线程卡顿,动画仍可流畅运行。
-
核心步骤
- 接收 位图 : 合成线程从 GPU 显存中获取已完成光栅化的图块位图
- 计算变换: 根据图层的transform、opacity等属性,计算每个图块需要应用的变换矩阵
- 绘制图块到合成层: 将每个图块的位图绘制到对应的合成层(按正确位置和变换)
- 合并合成层: 按照层叠顺序(z-index、堆叠上下文)将所有合成层合并成一张最终图像
- 提交显示: 将最终图像提交给GPU的显示缓冲区,等待屏幕刷新显示
-
性能关键点
- 优先使用「仅触发合成」的属性,使用
transform(位移/缩放/旋转) 和opacity。它们只影响合成, 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 内存不足时,浏览器会回退到 CPU 光栅化 |
| 合成 | GPU | 并行图层合并、变换矩阵运算(transform/opacity 仅触发此阶段) |
-
前期阶段( HTML 解析 → 绘制): CPU 主场这些阶段涉及复杂的逻辑判断、递归计算和指令生成,属于串行、高逻辑密度的任务,CPU 是唯一高效执行者;若主线程被阻塞(如长时间 JS 执行),会直接导致渲染卡顿。
-
后期阶段(光栅化 → 合成): GPU 主场这两个阶段是并行 、像素级运算,GPU 天生擅长处理大规模并行计算,因此性能远优于 CPU;
- 光栅化优先由 GPU 完成,但若GPU内存不足或图层过小,浏览器可能回退到CPU光栅化。
- 当元素形成独立图层时(如设置 will-change: transform/opacity,或天然的堆叠上下文),修改 transform(位移 / 缩放 / 旋转)和 opacity 仅触发合成阶段,不触发布局和重绘
三、重排、重绘
重排触发场景:
-
修改几何属性:
width、height、padding、margin、left/top等 -
增删/移动 DOM 节点:
appendChild、removeChild、insertBefore -
修改内容:
innerHTML、文本节点变化 -
窗口尺寸变化:
resize事件 -
强制重排:在样式修改后读取布局属性(如
offsetTop、clientWidth)
重绘触发场景:
-
颜色相关:
color、background-color、border-color(不修改宽度时) -
阴影相关:
box-shadow、text-shadow -
背景相关:
background-image、background-position、background-size -
文本样式:
text-decoration、font-style(不影响字体大小时) -
轮廓相关:
outline-color、outline-style -
可见性:
visibility(非display)
重排必然触发重绘,重绘不一定触发重排
四、渲染性能优化
| 渲染阶段 | 优化目标 | 核心优化手段 |
|---|---|---|
| 网络请求 | 减少阻塞 | 预解析、关键资源优化 |
| HTML解析 | 减少阻塞 | 精简HTML、JS异步 |
| CSS解析 | 减少阻塞 | 关键CSS内联、避免@import |
| 样式计算 | 减少耗时 | 简化选择器、避免通配符 |
| 布局 | 避免重排 | 批量操作、离线DOM |
| 分层 | 合理分层 | will-change、独立图层 |
| 绘制 | 减少重绘 | 优先使用 transform/opacity(仅触发合成)、避免大面积重绘区域 |
| 光栅化 | 加速光栅化 | 视口优先、GPU加速 |
| 合成 | 提升合成效率 | transform/opacity、图层管理 |
-
减少重排/重绘
- 批量修改 DOM 和样式(如使用 DocumentFragment 批量添加节点,或先隐藏节点 display: none,修改完成后再显示)。
- 避免频繁读取布局属性(如 offsetWidth、clientHeight、getBoundingClientRect()),若需多次读取,可缓存结果。
- 使用 transform 和 opacity 实现动画(仅触发合成,不触发布局和重绘),替代修改 width、height、top 等几何属性。
-
利用GPU加速
-
动画用
transform和opacity:transform(如translate、scale)和opacity的修改仅触发合成,不触发重排和重绘,是性能最优的动画实现方式
-
创建独立 图层:
- 对频繁动画的元素,用
will-change提示浏览器创建独立图层,提前做好优化准备,但不要滥用will-change,否则会占用过多GPU内存。
- 对频繁动画的元素,用
-
避免渲染阻塞
-
JS 优化:
- 首屏非必需的 JS 用
async/defer加载; - 大型 JS 文件用代码分割(Code Splitting),按需加载;
- 首屏非必需的 JS 用
-
CSS 优化:
- 外部 CSS 文件放在
<head>中(确保样式优先加载); - 非首屏 CSS 用媒体查询
media="print"等,不阻塞首屏渲染:
- 外部 CSS 文件放在