Skip to content

浏览器渲染原理

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,那是主线程的事。
    • 并发加载 + 主线程继续构建
  • 定时器与调度线程
    • 用于处理 setTimeoutsetIntervalrequestIdleCallback 等定时任务。
    • 与任务调度器(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树

DOMContentLoad

Load事件

made with ❤️ by ankang