will-change 之所以没有生效,通常是因为动画属性并没有进入合成渲染路径;真正容易受益的通常只有 transform、opacity 等少数属性,像 left、width 这类属性即使声明了也常常不会带来加速效果,而且 should 采用动态设置并结合双 requestAnimationFrame 清理,静态写死的方式现在基本已经失去优化意义。

will-change 没有效果,根本原因通常是动画属性不在合成层路径中
即便提前加上了 will-change: transform,CSS transition 还是出现卡顿,问题往往并不在这句提示本身,而在于实际参与动画的属性根本不是 transform 或 opacity。浏览器真正会对 will-change 做出优化响应的,主要就是这两个属性,外加极少数像 scroll-position 这样的特殊情况;而像 left、width、border-radius 这类属性,大多不会走 GPU 合成加速,相关声明通常也就等于被忽略。也就是说,CSS 里虽然写了 will-change,看起来像做了性能优化,但实际上未必真正生效。
常见错误包括:
- 写了
will-change: transform,但 transition 或动画目标实际修改的是left: 100px—— 浏览器依然需要逐帧重排和重绘 - 使用
transition: all 0.3s,其中夹杂了background-color或box-shadow—— 这些属性往往需要 CPU 参与绘制,容易拖慢整个动画性能 - 元素父容器带有
overflow: hidden+border-radius,导致子元素难以独立提升为图层,进而让will-change看起来失效
图层创建过多,会让 GPU 内存和渲染资源被提前占满
will-change 并不是简单理解成“开启硬件加速”就够了,它本质上更像是在提前通知浏览器:“这个元素可能要变化,请预留合成层资源。” 一旦元素应用了这个属性,浏览器通常会尝试为其分配独立图层,而这会持续占用 GPU 内存、纹理缓存以及合成资源。如果一个列表页里有 20 个以上的元素,同时统一写成 .item { will-change: transform; },那几乎就等于让大量图层长期驻留。结果也很明显:低端 Android 设备特别容易掉帧、发热,严重时甚至可能导致 WebView 崩溃。
真正合理的做法不是长期静态声明,而是按需动态控制:
- 只在用户真实交互即将发生的瞬间设置:
element.addEventListener('touchstart', () => element.style.willChange = 'transform') - 动画结束后必须恢复为
'auto'(不是空字符串):element.addEventListener('transitionend', () => element.style.willChange = 'auto') - 尽量不要依赖
scroll事件触发 —— 一旦节流不及时,就可能堆积大量未清理的图层状态
如果在 DevTools 中看不到橙色边框,说明 CSS 加速并没有真正开启
写了 will-change,也用了 transform,并不等于浏览器一定完成了图层提升和合成优化。要判断 CSS 动画卡顿的真正原因,必须验证底层渲染行为:
- 打开 Chrome DevTools → Cmd+Shift+P 输入 “Rendering” → 勾选
Layer borders:只有看到橙色边框,才说明对应元素确实创建了图层 - 再勾选
Paint flashing:如果动画区域之外也频繁出现绿色闪烁,说明页面重绘范围过大,这种情况单靠will-change也无法解决 - 使用 Performance 面板录制动画过程:如果
Layout或Update Layer Tree占比很高,问题多半不在合成层本身,而是主线程被 JavaScript 执行或样式读取阻塞了
移动端 touch 事件没有加 passive: true,手势和过渡动画自然不跟手
很多人以为页面卡顿一定是渲染性能问题,但实际在移动端,事件监听阻塞同样是高频原因。iOS Safari 和 Android Chrome 默认会对 touchstart/touchmove 进行同步等待,直到主线程确认是否调用阻止默认行为,这就容易让动画响应慢半拍。即便 will-change 设置完全正确,用户依然会明显感觉到滑动或手势操作发黏、不顺畅。
因此必须显式优化:
- 监听事件时加上
{ passive: true }:element.addEventListener('touchstart', handler, { passive: true }) - 配合使用
touch-action: pan-y(纵向滚动)或manipulation(保留点击),让浏览器跳过双击缩放判定带来的延迟 - 滚动过程中尽量避免在
touchmove回调里读取scrollTop或getBoundingClientRect()—— 这会触发强制同步回流,页面一滚就容易掉帧
will-change 只是性能优化提示,不是解决 CSS transition 卡顿的万能开关;它只有在 transform/opacity 动画真实发生时才更可能发挥作用,而且生命周期必须由 JavaScript 精准控制。如果长期写死在 CSS 中,往往不但无法提升性能,反而更容易带来额外的内存占用和渲染负担。