GPU 加速在前端的应用:从浏览器渲染流程到合成层
Category(分类): Browser Status: 已更新
本文尽量保留原文关于 CPU/GPU、浏览器渲染流程、Composite(合成)和合成层的讨论,并修正“GPU 一定比 CPU 快”“开启 GPU 加速就不会重排重绘”等容易误导的说法。浏览器内部实现会随版本和平台变化,文中的渲染阶段是便于理解的模型,不应当当成所有浏览器都完全相同的实现细节。
一、GPU 和 CPU 分别擅长什么?
GPU(Graphics Processing Unit,图形处理器)最初主要用于图形图像处理,现在也广泛用于视频解码、通用并行计算和机器学习等场景。CPU 则是面向通用任务的处理器,擅长复杂控制流、分支判断、低延迟响应和各种不同类型的任务。

GPU 通常包含大量相对简单的执行单元,适合对大量相似数据执行相同或相近的操作,例如对像素、顶点和纹理进行批量计算。CPU 的核心数量通常较少,但单核控制能力、缓存层次和分支预测能力更强,多个 CPU 核心同样可以提供真正的并行执行。
因此,不能简单地说“GPU 的浮点性能是某款 CPU 的多少倍”。不同型号、指令集、精度、内存带宽、任务分支和数据传输开销都会改变结果。GPU 也不是所有任务都更快:如果任务很小、分支复杂,或者需要频繁把数据在 CPU 与 GPU 之间搬运,使用 GPU 反而可能增加开销。
二、为什么 GPU 适合图形处理?
图形处理经常包含大量相互独立的计算:
- 把顶点坐标变换到屏幕空间;
- 为像素计算颜色、纹理和光照;
- 对图片进行缩放、滤镜和混合;
- 将多个已经绘制好的表面按顺序合成。
这类任务具有较高的数据并行性。GPU 可以把工作拆成许多相似的小任务交给不同的执行单元处理:

但“并行”并不代表所有数据都能同时无条件计算。任务之间存在依赖、分支或同步时,GPU 的优势会降低;GPU 的工作还要经过驱动、命令队列和显存/共享内存访问,不能脱离完整系统单独比较理论峰值。
三、浏览器页面是怎样绘制到屏幕上的?
简化后,一个页面通常会经历如下阶段:
- 解析:解析 HTML 生成 DOM,解析 CSS 生成 CSSOM;
- 样式计算(Style):计算每个节点最终适用的样式;
- 布局(Layout):计算盒子的尺寸和位置;
- 绘制准备(Paint):把背景、文字、边框、阴影、图片等绘制指令记录下来;
- 栅格化(Raster):把绘制指令转成位图,通常会按图块(tile)处理;
- 合成(Composite):把不同图层或表面按正确顺序、变换和透明度合成为最终画面;
- 显示:交给操作系统和显示设备呈现。

这不是严格的一条直线。浏览器会缓存样式、布局和栅格结果,只更新受影响的部分;动画、滚动、资源加载和合成可能在不同线程或不同时间片执行。某些阶段还可能并行或被跳过。
浏览器进程、合成线程与 GPU 进程
现代浏览器通常把页面放在渲染器进程中,并通过合成线程处理部分滚动和合成工作;GPU 进程负责与图形 API、GPU 设备交互。不同浏览器、操作系统和硬件可能采用不同的进程与线程安排:

因此,“CSS 属性由 GPU 处理”并不准确。一个更可靠的说法是:某些属性变化可以让浏览器只更新合成阶段,浏览器可能把相关内容放在独立的合成表面上,最终使用 GPU 或其他图形路径完成合成。栅格化、图片解码和绘制不一定全部由 GPU 完成。
四、Layout、Paint 和 Composite 的三种常见更新路径
原文用三种路径解释为什么某些动画更流畅。可以把它整理为以下模型:
1. 几何属性变化:可能触发布局和绘制
修改 width、height、margin、padding、top、left 等属性,可能改变元素和其他元素的几何关系:
box.style.width = `${nextWidth}px`
浏览器可能需要重新计算布局,再绘制受影响的区域,最后合成。影响范围取决于元素在布局树中的位置,不一定每次都导致整页重排,但频繁修改这类属性可能产生较高成本。
2. 绘制属性变化:通常跳过布局,但仍需要 Paint
颜色、阴影、背景和部分滤镜变化不一定改变布局尺寸,却可能使浏览器重新绘制像素:
box.style.backgroundColor = nextColor
绘制面积、文字、阴影、滤镜和图片复杂度都会影响成本。跳过 Layout 不等于免费。
3. 合成属性变化:有机会只更新 Composite
transform 和 opacity 是最常用于合成动画的属性:
.card {
transition: transform 200ms ease, opacity 200ms ease;
}
.card.is-moving {
transform: translate3d(0, -12px, 0);
opacity: 0.85;
}
如果浏览器已经为元素准备了合适的合成表面,动画过程中可能只改变变换矩阵或透明度,不必每一帧重新布局和绘制:


“可能只更新合成”是优化机会,不是规范保证。元素首次进入合成层、改变了其他属性、包含复杂滤镜或受到祖先/后代影响时,仍可能发生 Paint 或 Layout。


五、渲染层、合成层与图层树
旧资料中经常出现 WebKit 的 RenderLayer、GraphicsLayer 等术语。它们可以帮助理解历史实现,但不能直接当作今天所有 Chromium、Safari 或 Firefox 的公开 API。现代浏览器内部还会使用布局树、绘制块、合成层、图块和 GPU 表面等多个概念。
从开发者角度,可以这样理解:
- 页面首先产生绘制指令;
- 浏览器根据重叠、变换、滚动、视频、滤镜等因素决定哪些内容需要单独的合成表面;
- 合成器再按照层级、裁剪、混合和变换关系把这些表面组合起来;
- 单独的合成表面可以减少重复绘制,但会占用更多内存和纹理资源。

合成层的价值主要在于隔离更新范围和支持高效的变换、滚动或透明度动画,而不是“把所有计算都交给 GPU”。
六、如何合理使用硬件加速和合成动画?
1. 优先使用 transform 和 opacity
对于位移、缩放、旋转、淡入淡出等动画,优先使用:
.panel {
transition: transform 240ms ease, opacity 240ms ease;
}
.panel.is-entering {
transform: translateY(16px);
opacity: 0;
}
如果动画确实会改变文档流位置,transform 只是视觉移动,不会让后续内容重新排版。此时应先确认它是否符合交互和可访问性需求,不能为了性能改变布局语义。
2. 谨慎使用 will-change
will-change 是给浏览器的提示,不是“强制开启 GPU”的开关:
/* 对确实即将动画的元素短时间使用 */
.dialog.is-preparing {
will-change: transform, opacity;
}
浏览器可能为 will-change 元素提前创建合成资源。大量元素、长期设置或给页面所有节点设置 will-change 会增加内存、栅格化和合成成本。更好的做法是:
- 只对即将变化的少量元素使用;
- 在动画结束后移除提示;
- 先用 DevTools 验证是否真的改善了性能;
- 不要把
will-change: top, left当成避免布局的方案,它们仍可能触发布局。
3. translateZ(0) 不是通用秘籍
过去常见的 translateZ(0)、translate3d(0, 0, 0) 等写法,有时会促使旧版浏览器创建合成层,但现代浏览器有自己的合成决策,结果依实现、平台和元素内容而不同。盲目使用可能造成:
- 大量纹理占用 GPU 内存;
- 文本和图片重新栅格化后变模糊;
- 合成层数量过多;
- 图层上传、合成和内存带宽成为新瓶颈。
4. Canvas、WebGL 和视频
canvas、WebGL 和视频播放可能使用硬件加速路径,但是否使用、使用哪种路径由浏览器、驱动、编码格式、跨域策略和设备能力决定。不要仅凭元素类型断言“必然由 GPU 绘制”。
七、GPU 加速的代价和常见误区
1. 合成层不是越多越好
粗略估算一个 RGBA 纹理的内存开销时,可以用下面的关系帮助建立直觉:
纹理内存 ≈ 宽度 × 高度 × 4 字节 × DPR²
真实实现还会受到图块尺寸、格式、双缓冲、缓存和压缩方式影响。一个覆盖整个移动端页面、DPR 为 3 的合成层可能比想象中占用更多内存。
2. “不重排、不重绘”不是性能保证
即使动画只改变 transform 或 opacity,仍然可能受到以下因素影响:
- 主线程正在执行长 JavaScript 任务;
- 图片解码或字体加载阻塞;
- 合成层太大或数量太多;
- 阴影、滤镜、
backdrop-filter和混合模式成本很高; - 浏览器需要重新栅格化放大后的图片或文字;
- 设备 GPU 性能和内存带宽不足。
3. 60 FPS 不是唯一目标
60 Hz 屏幕每帧预算约为 16.7ms,120 Hz 屏幕每帧预算约为 8.3ms。动画性能应根据目标设备和刷新率观察实际帧耗时、长任务、输入延迟和掉帧,而不是只背诵“达到 60 FPS”。
4. 动画要尊重用户偏好
对会移动、缩放或闪烁的动画,应提供减少动效选项:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
八、如何验证是否真的变快?
不要只看代码中有没有 will-change 或 translate3d,应使用浏览器工具验证:
- 打开 Chrome DevTools 的 Performance,录制动画前后的主线程、Paint、Raster 和 Composite;
- 在 Rendering 面板开启 Paint flashing,观察是否每帧都重新绘制大面积内容;
- 在 Layers 面板检查合成层数量、尺寸和原因;
- 结合 FPS、长任务、内存和实际低端设备测试;
- 对页面级体验同时观察 LCP、INP、CLS,而不只看某个动画的帧率。
如果修改后只是让图层变多,却没有降低 Paint 或主线程工作量,就应撤销这项优化。
九、小结
- GPU 擅长高并行图形计算,但不是所有前端任务都适合 GPU;
- 浏览器会综合决定样式、布局、绘制、栅格化和合成路径,开发者不能直接“打开 GPU 加速”;
transform和opacity有机会走更轻量的合成路径,但不是绝对保证;will-change应少量、短时、经过测量后使用;- 合成层会消耗内存和纹理资源,层爆炸可能让性能更差;
- 应使用 DevTools 和真实设备验证,而不是套用固定的 GPU 加速秘籍。