浏览器实践
浏览器实践
1. 浏览器的渲染过程
从html、css、javascript到页面是如何实现的?
构建DOM树
一个页面的渲染是从html开始的,html描述了这个页面需要的部分资源(css、图片、javascript),也描述了页面的内容。在这之后会有css的样式计算,布局计算等。这样一个处理过程可以称为渲染流水线。
graph LR 构建DOM树 --> 样式计算 --> 布局阶段 --> 分层 --> 绘制 --> 分块 --> 光栅化 --> 合成
graph LR 构建DOM树 --> 样式计算 --> 布局阶段 --> 分层 --> 绘制 --> 分块 --> 光栅化 --> 合成
graph LR 构建DOM树 --> 样式计算 --> 布局阶段 --> 分层 --> 绘制 --> 分块 --> 光栅化 --> 合成
graph LR 构建DOM树 --> 样式计算 --> 布局阶段 --> 分层 --> 绘制 --> 分块 --> 光栅化 --> 合成
样式计算
在解析完html后,浏览器构建完DOM树,接着会着手样式的计算。
样式的来源有三个地方:
- 引入的外部css文件;
- style标签内描述的样式;
- 元素上内嵌的style属性;
- 浏览器会将所有的css样式转换为其可理解的结构 —— styleSheets。可以通过
document.styleSheets在浏览器中查看。 - 对于结构化之后的数据,浏览器还要对其属性进行标准化,例如
2em转换为36px,bold转换为700等。 - 最后根据css的继承规则和层叠规则计算出DOM树中每个节点的样式属性。
布局阶段
创建布局树(Layout Tree)
DOM树在整合了样式后,还缺少布局位置等信息,以及部分DOM节点并不需要实际绘制,例如head 标签,以及使用了display:none 属性的元素。浏览器会遍历DOM树中所有节点,将可见的节点放到布局树中,不可见的忽略。
布局计算
浏览器计算各个节点的坐标位置
分层(生成Layer Tree)
浏览器的页面实际分成了很多层,每个节点直接或间接地属于一个图层 拥有层叠上下文属性的元素会被提升为单独的一层,需要剪裁(clip)的地方也会被创建为图层。
图层绘制(tiles)
栅格化操作(raster)
合成和显示(draw quad)
渲染流水线大总结

- 渲染进程将HTML内容转换为能够读懂的DOM树结构。
- 渲染引擎将CSS样式表转化为浏览器可以理解的styleSheets,计算出DOM节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令DrawQuad给浏览器进程。
- 浏览器进程根据DrawQuad消息生成页面,并显示到显示器上。
重绘和重排
思考
如果下载CSS文件阻塞了,会阻塞DOM树的合成吗?会阻塞页面的显示吗?
不会阻塞DOM树的生成,因为浏览器在解析html时,发现<link rel=”styleSheet” … > 时会并行下载css文件,DOM解析会继续进行。
会阻塞渲染树的生成,构建渲染树需要:DOM Tree + CSSOM Tree → Render Tree,而CSSOM需要等待css文件下载解析后才能生成。只要CSSOM未完成,浏览器就不会构建渲染树,也不会Layout、Paint。
所以CSS会阻塞页面的显示,另外js脚本也会等css准备好后执行,以防js读取不到正确的样式。
不是所有的css文件都会阻塞页面渲染,如果css文件声明的设备不匹配当前设备,例如<link rel="stylesheet" href="print.css" media="print"> ,浏览器会忽略。
如何避免CSS阻塞渲染?
1、使用preload
<link rel="preload" href="style.css" as="style" onload="this.rel='stylesheet'">
特点:
- 不阻塞渲染
- 加载优先级高
- 使用完成后变成真实 CSS
适合:首屏 CSS 较少但不想阻塞
2、内联Critical CSS (关键渲染路径优化)
将首屏关键样式内联:
|
|
流程:
- 先渲染首屏
- 再异步加载剩余 CSS
适合:SSR 项目、性能优化要求较高的 SPA。
3、拆分CSS-按路由lazy load
特地:不阻塞渲染(dynamic import 下的 CSS 是非阻塞的)。
原理:dynamic import 下的 CSS 本质上是js模块,是被webpack、vite等打包工具打包后,通过js动态创建style或link来引入的,由于是运行时加载,不会影响首屏渲染。
适合:单页应用代码分割。
4、使用 media 测试加载非关键 CSS
|
|
适合:风格切换 / 不同设备样式差异大。
如何优化重绘重排?有哪些实践方法?
渲染进程中的线程 (由ChatGPT整理)
下面给你一份 Chrome(Chromium)渲染进程内部的线程模型全解析,不仅列出线程,还说明它们在整个渲染流水线中的作用、调度方式、何时互相协作,以及哪些工作会触发布局/绘制。
内容非常系统,可以当成参考文档。
🌈 渲染进程(Renderer Process)中的主要线程及职责
现代浏览器渲染进程并非只有“主线程 + Worker”,实际上包含多类专门线程,组合承担整个渲染与执行流水线。下面按照功能分层梳理。
✅ 1. 主线程(Main Thread / Render Thread)
主要职责
| 职责 | 描述 |
|---|---|
| 执行 JavaScript | JS 代码解释 + V8 执行 |
| 处理 DOM 与 CSSOM | DOM 构建、修改,CSS 解析与样式计算 |
| 布局(Layout / Reflow) | 节点尺寸、位置计算 |
| 绘制(Paint)生成 Display List | 生成绘制指令(Display List),但不进行光栅化 |
| 事件处理 | click、keydown、scroll(非合成的)等 |
| 微任务与宏任务调度 | Promise、setTimeout、requestAnimationFrame |
| 协调合成线程 | 通知图层变化、提交渲染更新 |
特点
- 浏览器页面的绝大多数“逻辑 + 样式 + 布局”都在这一线程执行
- 如果它被阻塞(长任务 >50ms)就会导致掉帧、卡顿
✅ 2. 合成线程(Compositor Thread)
主要职责
| 职责 | 描述 |
|---|---|
| 管理图层树(Layer Tree) | 主线程产生 layer tree 后,合成线程管理其更新 |
| 合成操作(Compositing) | 将不同图层合成为一帧,尽量不依赖主线程 |
| 驱动滚动、惯性滑动 | 浏览器提供的“线程内滚动”,不阻塞主线程 |
| 触发光栅化请求 | 通知 Raster 线程生成位图 |
| 控制 vsync 与帧生成 | 针对每次 vsync 提交新帧或复用之前的帧 |
特点
- 只要图层内容不变(例如 transform/opacity 动画),合成线程即可独立生成新帧
- 因此合成动画可达到 60/120 FPS,不被 JS 阻塞
举例:
|
|
transform 动画会在合成线程执行,不走主线程布局/绘制。
✅ 3. Raster 线程 / 光栅化线程(Raster Worker Threads)
主要职责
| 职责 | 描述 |
|---|---|
| 将 Display List 光栅化为位图(bitmap) | 实际生成像素图层 |
| GPU 加速光栅化 | 在 GPU 进程中使用 Skia + GPU |
| 图块化(Tiling) | 页面图层被分割成 tiles,按需光栅化 |
| 后台并行 | 多线程加速,主线程不阻塞 |
特点
- 通常是 多个线程,并行 raster(特别是大页面、复杂场景)
- 现代 Chrome 会使用 GPU rasterization,有性能优势
✅ 4. I/O 线程(在渲染进程中也存在)
主要用于:
| 功能 | 描述 |
|---|---|
| 网络 I/O | 不是网络进程,但某些资源/IPC 在此派发 |
| IPC 通信 | 与浏览器进程、GPU 进程通信 |
| 数据流调度 | 如 streaming、解码任务的派发 |
I/O 线程避免了主线程直接处理大量系统事件。
✅ 5. Image Decode Thread / 解码线程
用于:
- 图像解码(JPEG / PNG / WebP / AVIF)
- 部分视频帧解码
- 解码后的 bitmap 传递给 raster
现代浏览器会尽量使用 GPU 解码,但仍需要 CPU 解码线程。
✅ 6. Script Threads:Web Worker、Service Worker、Shared Worker
虽然都是“worker”,但本质是 渲染进程中的独立线程。
① Dedicated Worker(最常见的 Worker)
- 由页面创建
- 一个 worker → 一个独立 JS 线程
- 不能访问 DOM
- 用于计算任务
② Shared Worker
- 跨多个页面共享
- 仍在渲染进程中
- 有独立事件循环
③ Service Worker
- 在浏览器调度下运行
- 实际执行线程仍属于渲染进程
- 主要做网络代理、缓存、push等后台业务
Worker 的 JS 执行不会阻塞主线程,但会与主线程竞争 CPU。
✅ 7. V8 Helper Threads(V8 内部辅助线程)
V8 引擎内部有多个辅助线程:
| 名称 | 功能 |
|---|---|
| 编译线程(Compiler Thread) | 后台编译/优化 JavaScript |
| GC 线程(Garbage Collection helpers) | 并行或增量垃圾回收 |
| Profiler / Sampling Thread | 分析性能 |
这些线程也运行在渲染进程内部,但属于 V8 私有。
✅ 8. Task Scheduler(渲染进程调度器)
Chromium 有自己的任务调度器(Blink Task Scheduler),管理:
- 主线程任务
- 合成任务
- Worker 任务
- V8 编译任务
- 性能优先级(用户输入优先、渲染优先、后台任务等)
保证 UI 交互优先级最高,不被后台 worker 任务拖垮。
📌 总结:渲染进程中的完整线程列表
按功能分类如下(最清晰版本):
核心线程
- 主线程(Main Thread)
- 合成线程(Compositor Thread)
- Raster 线程(Raster Worker Threads)
辅助执行线程
- Web Worker 线程
- Service Worker 线程
- Shared Worker 线程
解码 / 绘制支持线程
- Image Decode Thread
- Video Decode Thread(可在 GPU 进程也有)
V8 内部线程
- JIT 编译线程
- GC 线程
- Profiler 线程
系统 / IPC 相关
- I/O Thread
- Scheduler Threads
📌 最简记忆版(你只需要记住这 6 个关键线程):
| 线程 | 核心作用 |
|---|---|
| 主线程 | JS + DOM + CSS + Layout + Paint |
| 合成线程 | 独立负责图层合成(Transform/Opacity 动画) |
| Raster 线程 | 光栅化生成 bitmap |
| Worker 线程 | 主线程卸载计算任务 |
| Image Decode 线程 | 解码图片 |
| I/O / IPC 线程 | 进程通信和事件派发 |