浏览器渲染原理
https://developer.chrome.com/blog/inside-browser-part1?hl=zh-cn
浏览器多进程多线程架构
多进程架构(Multi-Process Architecture)
Chrome 启动时会创建多个系统级别的进程(Processes),彼此相互隔离,主要包括:
- 浏览器进程(Browser Process)
- 只有一个,负责整个浏览器的主控。
- 管理:地址栏、书签栏、标签页管理、网络请求调度、资源管理、用户界面、进程调度。
- 拥有主线程(UI线程)和IO线程等。
- 渲染进程(Renderer Process)
- 每个标签页(Tab)/iframe/网站可用独立进程(通常为每个站点一个)。
- 执行 HTML、CSS、JavaScript 解析、布局、绘制。
- 沙箱隔离,增强安全性。
- 从A页面里面打开一个新的页面B页面,而A页面和B页面又属于同一站点的话,A和B就共用一个渲染进程。
- GPU进程(GPU Process)
- 处理页面和UI中的图形加速任务,如 WebGL、Canvas、视频播放等。
- 网络进程(Network Process)
- 负责网络请求、缓存、Cookie、下载。
- 早期属于浏览器进程,后独立出来以增强稳定性。
- 插件进程(Plugin Process)(已废弃或极少)
- 用于运行 Flash 等插件。
| 功能 | 进程名称 | 作用 |
|---|---|---|
| 主控 | 浏览器进程 | 管理窗口、标签页、网络请求调度 |
| 网络 | 网络进程 | 发起 HTTP 请求、处理响应 |
| 渲染 | 渲染进程 | 解析 HTML/CSS/JS,绘制页面 |
| 脚本执行 | 渲染进程中的 主线程(JS 引擎) | 执行 JS 脚本、处理事件 |
| 合成 | GPU 进程 | 加速图层合成与绘制 |
| 插件 | 插件进程 | 已逐渐淘汰 |
2. 每个进程内的多线程(Multi-Threading)
尤其是 渲染进程 内部,拥有多个线程:
- 主线程(UI Thread / Main Thread)
- 负责执行 JavaScript、解析 HTML/CSS、构建 DOM 和 CSSOM。
- 处理用户事件(点击、输入等)。
- 协调布局(Layout)和绘制(Paint)流程。
- 是最核心、最繁忙的线程。
- 合成线程(Compositor Thread)
- 负责将主线程生成的图层合成到屏幕上。
- 支持异步滚动、动画,尤其对页面流畅性和 GPU 加速有重要作用。
- 与 GPU 进程协同工作,提高性能。
- 光栅线程(Raster Thread / Rasterizer)
- 负责把绘图指令转化为位图(bitmap),即「栅格化」。
- 通常在 GPU 进程中执行,但也可能在 CPU 中由多个线程执行(如软件栅格化)。
- 工作线程(Worker Threads)
- Web Worker:用于执行与 UI 无关的 JavaScript 脚本。,避免阻塞主线程。
- Service Workers:拦截网络请求、缓存资源、支持离线功能。
- Worklet Threads:CSS Paint API、AudioWorklet 等使用。
- 预解析线程Script Streaming Thread
- 识别
<link>,<script>,<img>等资源。 - 提前发起请求,提高网络并发利用率。
- 但不能执行代码或构建 DOM,那是主线程的事。
- 并发加载 + 主线程继续构建
- 识别
- 定时器与调度线程
- 用于处理
setTimeout、setInterval、requestIdleCallback等定时任务。 - 与任务调度器(Task Scheduler)共同工作,管理优先级和任务队列。
- 用于处理
浏览器地址栏输入URL到页面渲染
用户输入 URL
↓浏览器进程 发起导航
↓网络进程 发起请求 → 返回响应
↓渲染进程(主线程)解析 HTML/JS/CSS
↓生成 DOM/CSSOM → Render Tree → Layout → Paint
↓绘图指令 → 栅格线程 → 位图
↓合成线程 + GPU → 显示页面
步骤 1:用户输入 URL,按下回车
- 浏览器进程
- 判断是否需要导航、是否命中缓存。
- 交给网络进程发起 HTTP/HTTPS 请求。
步骤 2:网络请求发送与响应接收
- 网络进程
- 建立 TCP/QUIC 连接,发送请求。
- [](https://www.chromium.org/Home/chromium-security/corb-for-developers)****
步骤1 2 https://www.yuque.com/yuqueyonghuyabgor/stiomf/pbguapn16vnzg2sq
步骤 3:创建或重用渲染进程
谷歌官方介绍:
https://developer.chrome.com/blog/inside-browser-part3?hl=zh-cn
- 浏览器进程
- 检查当前标签页是否已有对应的渲染进程。
- 若无,创建新的渲染进程,并分配权限(Site Isolation)。

步骤 4:文档解析(HTML)
- 渲染进程:主线程
- 逐行解析 HTML。
- 构建 DOM 树(Document Object Model)。
- 遇到
<script>阻塞,执行 JS。 - 下载并解析 CSS,构建 CSSOM(CSS Object Model)。
注意点
https://www.yuque.com/yuqueyonghuyabgor/aa116g/ogafbv3u9s41a7po?singleDoc# 《终极-浏览器加载资源顺序指南》
HTML解析过程中遇到css代码
HTML文档的解析,在解析前,预解析线程工作,率先发起请求下载HTML中外部的css、js文件。
开始逐行解析,遇到link标签,若此时该标签的css资源还没下载解析完成,主线程不会等待,会继续文档的解析,构建dom树,而css会在预解析线程中同步下载解析,完事后给到主线程,主线程生成cssom树
css的加载不会直接阻塞html的解析,但是会阻塞js的执行,阻塞js的执行进而会阻塞html解析(css在script之前)
- js中可能会调用
getComputedStyle()、读取 DOM 样式信息等,如果 CSS 没加载完成就执行,会导致样式不准确或闪烁。
HTML解析过程中遇到js代码
同步js代码
继续解析,如果遇到了script标签,会停止解析,等js文件下载完成后,开始执行js文件,js执行完成之后,继续HTML解析
js的下载我理解也是预解析线程中进行的,之所以会阻塞HTML解析,我理解根本原因是因为主线程要等js执行完成,因为
- js的执行可能会操作DOM
- 防止出现边构建边改动的情况,所以HTML主解析器必须停止,等js执行完,再继续构建DOM
async defer 参见文档
https://www.yuque.com/yuqueyonghuyabgor/aa116g/ogafbv3u9s41a7po?singleDoc# 《终极-浏览器加载资源顺序指南》
步骤 5:样式计算
- 主线程
- 合并 DOM 和 CSSOM,进行样式计算

步骤 6:布局(Layout / Reflow)布局树
- 主线程
- ``````


步骤 7:分层与层树(Layer Tree)
- 主线程
- 检查哪些元素需要独立图层(如 transform、opacity)。
- 分层好处:将来某个层发生改变,仅对该层进行处理,提升效率
- 滚动条、堆叠上下文、transform\opacity等样式都会过多过少的影响分层结果,也可以通过will-change属性更大程度影响分层结果
步骤 8:绘制(Paint)
- 主线程
- 将图层中的元素绘制为,用来描述该层应该怎么画出来。
- 完成绘制后,主线程将每个图层的绘制信息提交到合成线程、剩余工作由合成线程完成

步骤 9:合成:(Raster)+ 合成
- 光栅线程 / GPU 进程
- 将绘图命令转为位图(raster),准备上传到 GPU。
- 光栅化结果生成一块块的位图
- 合成线程 + GPU 进程
- 合成线程(Compositor Thread)计算最终合成顺序。
- ********
- 会标识出每个位图应该画到屏幕的那个位置,以及会考虑到旋转、缩放等变形
- 变形发生在合成线程、与渲染主线程无关、所以transform效率高
- 合成线程会把合成器****提交给GPU进程,由GPU进程产生系统调用,提交给GPU硬件,完成屏幕成像
渲染流程描述(白话版)
官网:
https://developer.chrome.com/blog/inside-browser-part3?hl=zh-cn#style_calculation
网络进程拉取html资源后,会产生一个渲染任务,并将其传递给渲染进程 -》 主线程的消息队列,在事件循环机制的作用下,渲染主线程从消息队列中取出任务,开启渲染流程;
渲染流程的主要步骤,html解析、样式计算、布局、分层、绘制、分块、光栅化、画。通过这个流水线完成页面的渲染。
第一步html解析:
渲染主线程进行html的解析,生成dom树(dom对象)
主线程完成cssom树的构建
主线程完成js代码的加载&执行(各种情况)
第二步样式计算 :
主线程会遍历得到的DOM树,依次为树中的节点计算他的最终样式,computed style,在这个过程中,很多预设值会变成绝对值,相对单位会变成绝对单位,完成后会得到一个携带样式的DOM树
第三步:布局
``````第四步:分层
。分层好处:将来某个层发生改变,仅对该层进行处理,提升效率
滚动条、堆叠上下文、transform\opacity等样式都会过多过少的影响分层结果,也可以通过will-change属性更大程度影响分层结果
第五步:绘制
主线程,完成绘制后,主线程将每个图层的绘制信息()提交到合成线程
第六步:分块
第七步:光栅化
最后:画
合成线程收集图块信息,创建合成器帧,合成器帧发送到GPU进程,给GPU硬件,完成成像
回流重绘
(1)回流
回流 的本质就是重新计算 layout 树。
当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。
为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的 回流 是异步完成的。
也同样因为如此,当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息。
浏览器在反复权衡下,最终决定获取属性(比如 dom.clientWidth)立即 回流。
(2)重绘
重绘 的本质就是重新根据分层信息计算了绘制指令。
当改动了可见样式后,就需要重新计算,会引发 重绘。
由于元素的布局信息也属于可见样式,所以 回流 一定会引起 重绘。
(3)最后总结
- 回流(也叫重排):当 DOM结构发生变化 或者 元素样式 发生改变时,浏览器需要重新计算样式和渲染树,这个过程比较消耗性能。
- 重绘:指元素的外观样式发生变化(比如改变 背景色,边框颜色,文字颜色color等 ),但是布局没有变,此时浏览器只需要应用新样式绘制元素就可以了,比回流消耗的性能小一些。
回流必定会发生重绘,重绘却可以单独出现 。回流的成本开销要高于重绘,而且一个节点的回流往往回导致子节点以及同级节点的回流, 所以优化方案中一般都包括,尽量避免回流。
总之,对元素执行回流操作之后,还有可能引起重绘或者合成操作,形象地理解就是“牵一发而动全身”。
引起回流
- 使用 CSS 动画代替 JavaScript 动画:CSS 动画利用 GPU 加速,在性能方面通常比 JavaScript 动画更高效。使用 CSS 的transform 和 opacity 属性来创建动画,而不是改变元素的布局属性,如宽度、高度等。
- 使用 translated3d 开启硬件加速:将元素的位移属性设置为 translated3d( 0,0,0 ),可以强制使用 GPU 加速。有助于避免回流,并提高动画流畅度。
- 避免频繁操作影响布局的样式属性:当需要对元素进行多次样式修改时,可以考虑将这些修改合并为一次操作。通过添加/移除 css类来一次性改变多个样式属性,而不是逐个修改。
- 使用 requestAnimationFrame:通过使用 requestAnimationFrame 方法调度动画帧,可以确保动画在浏览器的重绘周期内执行,从而避免不必要的回流。这种方式可确保动画在最佳时间点进行渲染。
- 使用文档片段(Document Fragment) :当需要在 DOM 中插入大量新元素时,可以先将这些元素添加到文档片段中,然后再将整个文档片段一次性插入到 DOM 中。这样可以减少回流和重绘的次数。(vue 虚拟dom的做法)
- 使元素脱离文档流:position: absolute/position: fixed/float:left(只是减少回流,不是避免回流)
- 使用 visibility:hidden 代替 display: none :visibility:hidden不会触发回流,因为元素仍然占据空间,只是不可见。而 display: none 会将元素从渲染树中移除,引起回流。
- 多样式调整的话,进行类的整合,隐藏后再处理
认识
cssom树

