4. 合成与层优化:什么是合成层、如何创建合成层
聊到页面切换流畅度,有个概念绕不开——合成层。
我刚开始做性能优化时,总觉得这玩意儿是浏览器底层的事,跟我写 CSS 有什么关系?直到有一次,一个简单的侧边栏动画,在低端机上卡成 PPT。我用 DevTools 一查,发现整个页面被当成一张大图在重绘……嗯,从那以后,我再也不敢忽视合成层了。
4.1 什么是合成层
说白了,合成层就是浏览器给页面元素分配的一个“独立画板”。
正常情况下,浏览器把整个页面画在一张图上。如果某个元素动了,它得把整张图重画一遍——这叫重绘。但如果这个元素被提升到合成层,它就拥有了自己的画板。移动它的时候,浏览器只需要移动这个画板,其他部分完全不动。
你想想看,这效率能差多少?
核心结论:合成层上的 transform 和 opacity 动画,不触发重绘,只触发合成。这是 60fps 流畅度的基石。
4.2 如何创建合成层
浏览器不是随便给元素分配合成层的。它有一套触发条件。我挑几个最常用的讲:
- transform:使用
translate3d()、translateZ()、scale3d()等 3D 变换时,浏览器会自动创建合成层。 - opacity:对 opacity 做动画时,如果元素本身没有其他布局依赖,也会被提升。
- will-change:显式告诉浏览器“我要变了”,比如
will-change: transform。 - video、canvas、iframe:这些元素天生自带合成层。
- filter:某些 filter 效果也会触发。
我个人习惯,做动画时优先用 transform 和 opacity。为什么?因为它们不改变布局,不触发重绘,性能开销最小。
/* 推荐:只触发合成 */
.card {
transition: transform 0.3s ease, opacity 0.3s ease;
}
.card:hover {
transform: translateY(-4px);
opacity: 0.9;
}
/* 不推荐:触发重绘 */
.card {
transition: left 0.3s ease, top 0.3s ease;
}
.card:hover {
left: 10px;
top: -4px;
}
小技巧:如果你不确定某个属性会不会触发重绘,可以去 csstriggers.com 查。我每次写新动画前都会看一眼,养成习惯了。
4.3 层爆炸问题与层压缩
合成层不是越多越好。你想想看,每个合成层都是一块独立的内存。移动端 GPU 内存本来就紧张,层一多,直接崩给你看。
我在项目中遇到过一个问题:一个列表页,每个列表项都加了 will-change: transform,想着“提前告诉浏览器,性能更好”。结果页面滚动时掉帧严重。一查,合成层数量超过 50 个。这就是典型的层爆炸。
避坑指南:我曾经在一个无限滚动列表里,给每个 item 都加了 translateZ(0) 来“硬件加速”。结果页面在低端机上直接白屏。后来才知道,合成层数量超过设备限制,浏览器会直接丢弃层,导致渲染异常。
那怎么解决?层压缩。
浏览器其实有层压缩机制——如果多个合成层在空间上重叠,且没有复杂的 3D 变换,浏览器会尝试把它们合并到同一个层里。但这不是万能的。我们开发者能做的是:
- 只在需要动画的元素上创建合成层,不要滥用
translateZ(0)。 - 控制合成层数量,移动端建议不超过 10 个。
- 使用
will-change要谨慎,动画结束后移除。 - 避免层重叠,重叠的合成层会增加合成开销。
| 场景 | 推荐做法 | 不推荐做法 |
|---|---|---|
| 卡片 hover 动画 | 使用 transform + opacity |
使用 left / top |
| 列表滚动 | 只给当前可见项创建合成层 | 给所有列表项加 translateZ(0) |
| 弹窗动画 | 弹窗本身用合成层,背景不用 | 弹窗和背景都创建合成层 |
| 无限滚动 | 回收不可见项的合成层 | 一直保留所有项的合成层 |
4.4 实战:利用 Chrome DevTools 分析层
光说不练假把式。我们打开 Chrome DevTools,看看怎么分析合成层。
步骤很简单:
- 打开 DevTools(F12)。
- 点击右上角三个点 → More tools → Layers。
- 你会看到一个 3D 视图,展示当前页面的所有合成层。
在这个面板里,你可以:
- 查看层数量:左上角会显示总层数。超过 20 个就要警惕了。
- 查看层大小:每个层都有宽高信息。如果某个层特别大(比如整个页面),说明没被拆分。
- 查看层创建原因:鼠标悬停在一个层上,会显示它为什么被提升为合成层。比如
will-change: transform或video。 - 查看内存占用:每个层都有内存占用数据。加起来就是总 GPU 内存消耗。
我的习惯:每次做完动画优化,我都会打开 Layers 面板看一眼。如果层数量从 30 降到了 8,内存占用从 200MB 降到了 50MB,那基本就稳了。
还有一个更快的办法——Rendering 面板。勾选 Layer borders,页面上会显示橙色边框,有边框的就是合成层。一眼就能看出哪些元素被提升了。
我举个例子。之前优化一个商品详情页,图片轮播卡顿。打开 Layer borders 一看,整个轮播区域只有一个合成层。但轮播里有 5 张图片,每张图片都在做 translateX 动画。按理说每张图片都应该有自己的合成层。检查代码后发现,图片的父容器加了 overflow: hidden,导致子元素无法独立创建合成层。去掉父容器的 will-change,给每张图片单独加 translateZ(0),卡顿问题就解决了。
你看,有时候问题就藏在这些细节里。
最后总结一下:合成层是性能优化的利器,但用不好就是双刃剑。记住三个关键点——用 transform 和 opacity 做动画、控制层数量、用 DevTools 验证。做到这三点,页面切换流畅度基本不会差。