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,看看怎么分析合成层。

步骤很简单:

  1. 打开 DevTools(F12)。
  2. 点击右上角三个点 → More tools → Layers。
  3. 你会看到一个 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),卡顿问题就解决了。

    你看,有时候问题就藏在这些细节里。

    合成层优化决策流程 开始分析 检查合成层数量(目标 < 10) 层数 > 10? 或内存 > 100MB? 是 否 执行层压缩 移除不必要的 will-change 检查动画属性 是否用了 transform/opacity? 层状态良好 检查动画是否流畅 验证 FPS 是否达标 优化完成 提示: 层数量 < 10 且内存 < 100MB 是移动端的健康指标

    最后总结一下:合成层是性能优化的利器,但用不好就是双刃剑。记住三个关键点——用 transform 和 opacity 做动画、控制层数量、用 DevTools 验证。做到这三点,页面切换流畅度基本不会差。