性能
预览会实时播放你的合成,因此任何渲染时间超过 33ms(在 30fps 下)的帧都会表现为卡顿。本页介绍超出此预算的模式以及如何发现它们。
预览 vs. 渲染
渲染会逐帧捕获并拼接成视频。慢帧会延长渲染时间,但你不会看到停顿——你看到的是完成后的 mp4。
预览则以实时方式执行相同的工作。如果一帧需要 200ms 来绘制,你就会看到 200ms 的冻结。
这就是为什么"渲染看起来正常,预览卡顿"在绘制密集型合成中是预期行为。这不代表预览坏了——而是单帧的开销对实时播放来说太大了。
高开销的 CSS 模式
以下是导致预览帧率降至 30fps 以下的最常见模式。
backdrop-filter: blur()
每个 backdrop-filter: blur(radius) 在大面积区域上采样时,都会强制合成器从元素后面读取像素、对其运行模糊核,然后合成结果。开销与模糊面积和半径成正比。
叠加的模糊层会成倍增加开销。八层渐进增大的半径(1、2、4、8、16、32、64、128px)在中端 GPU 上处理 1920x1080 区域时,每帧轻松达到 200ms。
解决方法:
- 叠加层最多保持 2-3 层,手动调整半径
- 避免在大面积区域使用
blur(128px)或blur(64px)——最大半径主导开销 - 对于静态模糊,渲染一次到 PNG 然后使用普通
<img>覆盖层
filter: blur() 和 filter: drop-shadow()
与 backdrop-filter 相同的原理,但应用于元素本身而非其后面。在小元素上没问题,在大元素上开销很大。
大量元素的阴影
在少量元素上使用 box-shadow 和 text-shadow 没问题。在数十个同时动画的元素上,合成器每帧都会重新光栅化每个阴影层。
带 mask-image 的大型渐变
与 backdrop-filter 结合使用时,mask-image 可能强制额外的合成器通道。如果同一元素上同时使用两者,请考虑是否确实需要。
图片尺寸
图片源分辨率比文件大小更重要。Chrome 在显示 JPEG 和 PNG 之前会将它们解码为原始 RGBA 位图——解码后的位图为:
bitmap_bytes = width × height × 4
一张 7000×5000 的源图片解码后为 140MB,无论磁盘上的 JPEG 是 2MB 还是 5MB。
**经验法则:**将源图片调整到最多画布尺寸的 2 倍。对于 1920x1080 的画布,3840x2160 的源图片已经绰绰有余。超过此尺寸就是在为永远不会显示在屏幕上的内存和纹理上传开销买单。
# ImageMagick one-liner to downsize a directory of images
mogrify -path resized -resize 3840x3840\> *.jpg
测量慢合成
不要猜测——测量。Chrome DevTools 拥有你需要的一切。
运行预览
启动预览服务器并在 Chrome 中打开:
npx hyperframes preview
打开 DevTools → Performance
Cmd+Option+I(macOS)或 Ctrl+Shift+I(Linux/Windows),然后切换到 Performance 标签页。
在播放时录制
点击录制按钮,在预览中点击播放,让其在容易卡顿的场景上运行 3-5 秒,然后停止录制。
查看主线程轨道
查找长任务(在时间轴中标记为红色)。展开最高的柱形并检查 Chrome 的标签:
- Composite Layers / Paint 持续时间较长 = 合成器开销(backdrop-filter、阴影、大型纹理)
- Decode Image = 首次绘制时的图片解码(在 Chrome 131+ 中很少见,图片默认在非主线程解码)
- Layout / Recalculate Style = 脚本导致的布局抖动
- Script = JS 工作(在合成中很少见,检查作者脚本)
一旦你知道哪个类别占主导,就知道该改什么了。
💡 Tip
一个合成在单独运行时能达到 60fps,但仅在特定场景卡顿,通常是合成开销问题。检查那些场景中哪些层变为可见。
当预览不可避免地卡顿
有些合成确实因开销过大而无法实时播放。如果你已经尽力减少开销但预览仍然卡顿,渲染为 mp4 然后观看输出是一个可行的工作流——渲染仍然是准确的。
npx hyperframes render --quality draft --output preview.mp4
草稿质量渲染速度快,除了编码器级别的细节外,视觉效果与最终渲染非常接近。