浏览器渲染流程与 Composite(合成)
Category(分类): Browser Status: 已更新
本文保留早期文章中“渲染层合并”的学习路线,同时把 WebKit/旧版 Chrome 的内部名词与现代浏览器实现区分开。RenderLayer、GraphicsLayer、纹理和 GPU 是理解历史资料的好入口,但它们不是 Web 平台 API,也不是今天所有浏览器都完全相同的内部结构。
一、从请求到像素
页面从网络到屏幕,通常可以抽象为:
导航
-> DNS / 连接 / TLS / HTTP
-> HTML、CSS、JavaScript 和其他资源
-> DOM、CSSOM、样式计算
-> Layout / fragment tree
-> Paint display list
-> Layerize / property trees
-> Rasterize tiles
-> Composite / Draw
-> 屏幕

这不是每一帧都必须完整执行的固定流水线:
- 没有 DOM 或样式变化时,不必重新布局;
- 仅改变某些合成属性时,可能只更新合成状态;
- 只改变颜色、背景或阴影时,可能跳过布局但仍需重新绘制;
- 滚动、输入、动画、视口变化和资源加载可能触发不同范围的更新;
- 浏览器也可能合并、延迟、取消或增量处理工作。
1. Layout、Paint、Raster 和 Composite 分别做什么
- Layout:计算参与布局的盒子或 fragment 的尺寸和位置;
- Paint:把背景、文本、边框、阴影、图片等记录为绘制指令/显示列表;
- Raster:执行绘制指令,生成图块位图或纹理;
- Composite:按照层、裁剪、变换、透明度和滚动状态,把已栅格化内容合成为最终画面。
早期文章把 Paint 也称作“绘画”,把 Raster 也混称为“光栅化”。学习时应把它们区分开:记录绘制指令和执行绘制指令不是同一件事。
二、什么是层
1. 层叠上下文不是合成层
CSS 的层叠上下文决定绘制顺序和层叠关系,常见创建条件包括根元素、定位元素配合 z-index、非 1 的 opacity、transform、filter、isolation: isolate、某些 will-change 值等。
但层叠上下文不等于浏览器一定会创建独立的合成层。一个元素可以创建层叠上下文,却仍然和其他内容一起绘制;浏览器是否把它提升为独立合成资源,取决于动画、滚动、裁剪、视频、重叠关系、平台能力和内存预算。
2. RenderLayer 和 GraphicsLayer:历史模型
早期 WebKit/Chrome 资料常使用下面的概念:
RenderLayer:围绕 DOM 子树和层叠关系组织渲染层;GraphicsLayer:可以作为独立绘制/纹理提交的图形层;GraphicsContext:记录该层的绘制操作;- 纹理:可以理解为供合成阶段使用的位图数据。
这个模型仍有助于解释“为什么重叠和透明度需要正确的绘制顺序”,但现代 Chromium 的实现还包括布局片段、属性树、绘制块、合成图层、Viz 和 GPU/CPU 栅格化路径。不要把旧名词当成 Chromium 当前所有代码路径的精确类图。

三、哪些情况可能产生合成层
浏览器的合成启发式会持续变化,以下只能作为常见方向,不能当作保证:
- 正在执行的
transform或opacity动画; - 视频、WebGL、部分 canvas 和需要独立更新的滚动内容;
- 固定/粘性定位、滚动容器、裁剪和遮罩等复杂场景;
- 某些
filter、混合和 3D 变换; will-change提前提示浏览器;- 为了隔离重绘区域或满足合成顺序而进行的内部提升。
以下说法都过于绝对:
- 设置
z-index就一定创建合成层; - 设置
opacity就一定把元素交给 GPU; - 使用
translateZ(0)就一定更快; - 每个 DOM 节点都有一个独立图层;
- 所有 transform 都不需要 Paint。
可以使用 DevTools 的 Layers、Rendering 和 Performance 面板确认某个版本、设备和页面的实际合成原因。
四、绘制列表和图块
渲染主线程会为需要更新的内容记录绘制操作,例如:
绘制背景 -> 边框 -> 文本 -> 阴影 -> 图片 -> 子元素

绘制列表通常会被划分为适合处理的图块。图块大小、数量、优先级和栅格化方式由浏览器和设备决定,不应把 256 × 256 或 512 × 512 当成固定规范。

现代浏览器可能使用 GPU 栅格化,也可能使用 CPU 或在不同内容、驱动和设备条件下回退。即使最终合成使用 GPU,生成绘制列表、样式计算和部分栅格化也可能占用主线程或 CPU。
低分辨率占位和首屏
一些浏览器为了尽快显示首屏,可能先处理视口附近或低质量的内容,再替换成完整结果。这是实现优化,不是所有浏览器、资源和页面都遵循的固定步骤。
五、合成为什么可能更快
当动画只改变合成器能够处理的属性时,浏览器可以复用已经绘制和栅格化的位图,只更新变换矩阵、透明度或滚动偏移:
.card {
transition: transform 240ms ease, opacity 240ms ease;
}
.card.is-moving {
transform: translate3d(0, 0, 0);
opacity: 0.8;
}
这可能跳过布局和 Paint,但并不意味着 JavaScript、事件处理或主线程永远不影响动画。长任务、频繁样式变化、内容重绘、内存压力和合成器无法处理的输入事件仍可能造成卡顿。
will-change 的正确用法
will-change 是提示,不是“打开 GPU 开关”:
const panel = document.querySelector('.panel')
function prepareAnimation() {
panel?.style.setProperty('will-change', 'transform')
}
function finishAnimation() {
panel?.style.removeProperty('will-change')
}
实际项目中更适合在动画即将开始前设置,并在结束后移除。长期给大量元素设置会提前分配资源、增加内存占用、扩大绘制区域,甚至降低性能。will-change: top 或 will-change: left 也不会把布局属性变成合成属性;改变它们仍可能触发布局。
六、隐式合成和层爆炸
旧版 Chrome 资料常把“一个低层叠级元素被提升后,上方重叠元素也被提升”的现象称为隐式合成,并用它解释层爆炸。这个现象在某些实现和场景中确实可能出现,但现代浏览器会通过合成原因、重叠测试、图层合并和资源预算做更多优化,不能使用一条旧规则推断所有图层数量。
层爆炸的实际风险包括:
- 大量独立位图或纹理占用 GPU/系统内存;
- 图层上传、栅格化和合成调度成本增加;
- 高 DPR 屏幕上的纹理尺寸被放大;
- 过度使用
will-change或 3D 变换导致滚动和动画更差。
排查时应在 Performance 中录制真实交互,结合 Layers/Rendering 查看图层尺寸和合成原因,而不是盲目给每个元素加 z-index 或 translateZ(0)。
七、常见优化方式
- 先减少需要绘制的内容:缩小绘制区域,避免超大阴影、复杂滤镜、巨大透明层和不必要的重叠;
- 动画优先尝试
transform和opacity:确认实际 trace 中确实跳过了不必要的 Layout/Paint; - 控制合成层数量和尺寸:合成层不是越多越好,尤其要注意移动端和高 DPR;
- 批量更新 DOM 和样式:合并 class 变化,避免读写交错造成强制同步布局;
- 使用 CSS containment 或
content-visibility做有边界的隔离:先验证布局、可访问性和滚动占位是否符合需求; - 使用
requestAnimationFrame组织视觉更新:不要把它误解为强制每次都能产生一帧; - 用工具验证:以长任务、Layout、Paint、Raster、Composite、LCP 和 INP 等真实数据为依据。
八、如何在 DevTools 中观察
Chrome DevTools 的 Performance 录制可以查看:
- Main 线程的 JavaScript 和长任务;
Recalculate Style、Layout、Paint;- 合成、栅格化和帧时间;
- Layout Shift、交互延迟和资源加载;
- 高级 Paint instrumentation 生成的绘制详情。
Rendering 面板可辅助显示 FPS、Paint flashing、Layout Shift regions 和 layer borders。Layers 面板适合查看图层大小、原因和绘制内容,但面板名称、入口和展示细节会随 DevTools 版本变化。
九、结论
Composite 的价值不是“把所有工作交给 GPU”,而是让浏览器在合适的情况下复用已经完成的绘制结果,只更新变换、透明度、滚动或层之间的关系。正确的优化顺序是:
测量 -> 找到 Layout/Paint/Raster/Composite 瓶颈
-> 减少内容和更新范围
-> 选择合适的 CSS/DOM 策略
-> 再次测量验证
不要把历史文章中的固定层规则、translateZ(0) 或“硬件加速万能”当作性能军规。