- Published on
浏览器架构与渲染流程
- Authors
- Name
- 高天
- Github
- @gtlab2023
概念
概念/术语 | 定义 |
---|---|
DOM (文档对象模型) | HTML文档的内部树状结构,表示元素及其关系 |
CSSOM (CSS对象模型) | CSS规则的结构化表示 |
计算样式 (Computed Style) | 每个节点的最终样式 |
布局树 (Layout Tree) | 基于DOM和样式计算出的视觉结构,用于确定位置和大小。 |
绘制记录 (Paint Records) | 描述如何绘制元素的指令。 |
合成 (Compositing) | 将多个图层组合成最终屏幕图像的过程。 |
图层 (Layers) | 可独立更新的页面部分,某些元素(如position: fixed )会创建独立图层。 |
瓦片 (Tiles) | 页面的小块区域,便于光栅化和缓存,提升渲染效率。 |
光栅化 (Rasterization) | 将矢量图形转为位图 |
重新布局 (Reflow) | 因DOM或CSS更改触发的布局重新计算,可能影响性能。 |
重绘 (Repaint) | 当元素外观更改时,重新绘制部分页面。 |
GPU加速 (GPU Acceleration) | 使用图形处理单元加速渲染任务,提升复杂图形和动画性能。 |
Draw Quad | Draw Quad(绘制四边形)是合成线程(Compositor Thread)在将页面内容渲染到屏幕上时使用的一种核心概念。它本质上是一个绘制指令,表示将某个特定的图像区域(通常是光栅化后的位图或纹理)绘制到屏幕上的某个位置。Draw Quad 是合成阶段的一个关键中间产物,用于将多个图层(Layers)和瓦片(Tiles)组合成最终的屏幕图像 |
测试工具
webpagetest:在资源URL左侧用橙色圆圈标记渲染阻塞资源
工作原理和过程
浏览器架构
浏览器架构
- Browser Process: 控制应用程序的“chrome”部分,包括地址栏、书签、后退和前进按钮。还处理网络浏览器中不可见的部分,例如网络请求和文件访问
- Render Process: Controls anything inside of the tab where a website is displayed
多进程的好处
每个选项卡的都开一个渲染进程,当一个进程没有响应时,不会影响其他选项卡内容的渲染
节省更多内存-Chrome中的服务化
当 Chrome 在强大的硬件上运行时,它可能会将每个服务拆分为不同的进程以提供更高的稳定性,但如果它在资源受限的设备上,Chrome 会将服务整合到一个进程中以节省内存占用
Per-frame renderer processes -Site isolation
Site isolation为每个站点iframe运行单独的渲染器进程。同源策略是网络的核心安全模型,它确保一个站点在未经同意的情况下无法访问其他站点的数据
渲染流程
解析HTML(DOM tree)
主线程(Main Thread)开始解析并构建文档对象模型(DOM)->DOM Tree
预加载扫描器(Preload Scanner):
主线程在解析HTML的同时,运行一个轻量级的预加载扫描器。
扫描器识别并提前加载子资源,例如:
提前请求图片。
加载CSS文件。
加载JavaScript文件。
优化加载优先级:
异步加载脚本,不阻塞HTML解析
延迟到HTML解析完成后执行。
提示浏览器提前加载关键资源
DOM树是后续渲染的基础,预加载扫描器通过并行加载资源减少等待时间
样式计算(DOM tree+CSSOM->Composer tree)
CSSOM与DOM树共同决定了页面的视觉表现,计算样式是布局的基础
- CSS解析
- 主线程读取CSS文件(包括外部CSS、内联样式)。
- CSS解析器将CSS代码转换为样式表对象(CSSOM,CSS Object Model)
即使不添加样式,dom结构自己也有默认样式
- 计算样式(Computed Style)
- 主线程遍历DOM树,为每个节点匹配CSS规则,计算最终样式->composer tree
布局(composer tree=>layout tree)
有了DOM树和计算样式,浏览器进入布局阶段,生成布局树并计算元素的位置和大小,详细了解这部分内容可以阅读这里,布局树定义了页面元素的最终位置和大小,是绘制的前提
- 布局树(Layout Tree)构建
- 布局树基于DOM树和CSSOM,但有所筛选:
- 排除display: none的元素(完全不可见)。
- 包括伪元素
- 布局树节点对应于页面的视觉结构
- 布局树基于DOM树和CSSOM,但有所筛选:
- 盒模型计算
- 位置和大小计算
绘制(layout tree->Paint Records)
布局完成后,浏览器生成绘制指令,准备将页面内容渲染到屏幕上,绘制阶段将布局树的视觉信息转化为像素级的渲染指令
- 生成绘制记录(Paint Records)
- 绘制顺序由z-index和堆叠上下文决定
- 性能影响:
- 绘制是CPU密集型任务,频繁更新(如动画)可能导致性能问题。
- 浏览器的目标是保持60帧每秒(16.6ms/帧),若主线程被JavaScript阻塞,可能导致卡顿
合成
绘制完成后,主线程将任务移交给合成线程(Compositor Thread),完成最终图像的生成和显示
- 提交绘制记录
- 主线程将绘制记录提交给合成线程,释放自身以处理其他任务
- 图层(Layers):合成线程将页面分成多个图层
- 瓦片(Tiles):每个图层被进一步分成小块(瓦片),通常为256x256或512x512像素。
- 瓦片化便于缓存和并行处理
- 光栅化(Rasterization:合成线程将瓦片转换为位图(Bitmap),并存储到GPU内存中
- GPU加速的光栅化显著提升性能
- 生成Draw Quad:合成线程为每个瓦片创建 Draw Quad,指定其纹理和屏幕坐标
- 考虑图层间的叠放顺序和透明度
- 显示(Viz组件):
- 合成线程将 Draw Quad 列表发送给浏览器进程的 Viz 组件,Viz 组件利用 GPU 将 Draw Quad 渲染为最终图像,显示在屏幕上。
- 为加快初次渲染,Viz可能先显示低分辨率图像,随后替换为高质量版本
实践
非快速滚动区域的优化
事件委托会将目标元素区域都标记为非快速滚动区域,这样合成器线程每次都要等待主线程工作后才会工作,就丧失了流畅的用户体验,可以通过在监听事件加passive:true
解决,这向浏览器提示您仍想在主线程中收听事件,但合成器也可以继续合成新帧
document.body.addEventListener(
'touchstart',
(event) => {
if (event.target === area) {
event.preventDefault()
}
},
{ passive: true }
)
获取完整的事件触发
对于大多数 Web 应用程序,合并事件应该足以提供良好的用户体验。但是,如果您正在构建诸如绘图应用程序并根据 touchmove坐标放置路径之类的东西,则可能会丢失中间坐标以绘制平滑线。在这种情况下,可以使用getCoalescedEvents指针事件中的方法来获取有关这些合并事件的信息
window.addEventListener('pointermove', (event) => {
const events = event.getCoalescedEvents()
for (let event of events) {
const x = event.pageX
const y = event.pageY
// draw a line using x and y coordinates.
}
})
性能优化
GPU加速
.transform-gpu {
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate))
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
scaleY(var(--tw-scale-y));
}