先记住这个答案
浏览器把页面渲染分成三步:布局、绘制、合成。合成是最后一步,GPU 把各个层拼在一起输出到屏幕。如果一个元素在自己的合成层上,修改它的 transform 或 opacity 只需要重新合成,不用重排也不用重绘,所以动画非常流畅。will-change: transform 就是告诉浏览器「这个元素的 transform 马上要变」,浏览器会提前把它提升到独立合成层,动画一开始就能直接跑在 GPU 上。
- 变换与透明度动画常可走合成
will-change提前创建合成层,减少首帧延迟- 合成层越多内存越大,不能滥用
- 动画结束后应移除
will-change
哪些情况会自动创建合成层
不是只有 will-change 才能创建合成层。浏览器遇到以下几种情况时会自动提升元素。
/* 3D 变换 */
.box-a { transform: translateZ(0); }
.box-b { transform: translate3d(0, 0, 0); }
/* will-change 提示 */
.box-c { will-change: transform; }
.box-d { will-change: opacity; }
/* 固定定位 */
.box-e { position: fixed; }
/* 正在播放的 CSS 动画(涉及 transform/opacity) */
.box-f { animation: slide 0.3s ease; }除了这些,<video>、<canvas>、<iframe> 等元素也会自动获得合成层。还有一种叫「隐式提升」——如果一个元素的合成层在另一个元素上面,被压在下面的元素也可能被提升,防止渲染顺序出错。
用 Chrome DevTools 的 Layers 面板可以看到当前页面有多少个合成层、每个层占多大内存。打开方式是 DevTools > More tools > Layers。
will-change 的正确用法
will-change 不是写上就好了。最理想的用法是在动画即将开始之前加上,动画结束后移除,这样合成层只在需要的时候存在。
.card {
transition: transform 0.3s ease;
}
.card:hover {
will-change: transform;
transform: scale(1.05);
}这里 will-change 写在 :hover 上,鼠标移上去时浏览器才创建合成层,移走后自动回收。但严格来说,最佳时机是在 hover 之前就准备好——用 JS 监听 mouseenter 加上 will-change,transitionend 时移除。不过对大多数场景来说,写在 :hover 里已经足够。
如果动画是页面加载时就要播放的,可以先在元素上写 will-change,动画播完后用 JS 移除。一直写着不移除,浏览器会一直保留那块 GPU 纹理,白白占内存。
滥用合成层的后果
把页面上每个会动的元素都加上 will-change: transform,听起来是在优化,实际上可能更卡。每个合成层都需要一块 GPU 纹理来存储它的位图,一个 300x200 的元素在 2 倍屏上就是 300x200x4x4 = 960KB。一个列表有 100 条,每条都独立一层,就是近 100MB 的 GPU 内存。
移动端 GPU 内存有限,超出后浏览器会开始频繁换入换出纹理,反而比不提升更卡。Chrome 有一个内部策略叫「层压缩」,会把一些不必要的层合并回去,但这不是所有浏览器都有的保障。
所以原则是:只给真正需要流畅动画的元素加 will-change,用完就移除。
容易答错的地方
- 以为
will-change本身能让动画变快 will-change只是提前创建合成层,减少的是动画「第一帧」的延迟。如果动画本身改的不是transform和opacity,而是width、height、left这些触发重排的属性,合成层并不能帮上忙——每帧还是要走完整的布局和绘制流程。- 以为
transform: translateZ(0)和will-change: transform完全等价 - 两者都能触发合成层提升,但
will-change语义更清晰,而且浏览器可以在不需要时自动回收。translateZ(0)是早期的 hack,它实际上修改了元素的渲染位置(虽然偏移量是 0),在某些场景下会影响子元素的 3D 变换表现。 - 把
will-change写在全局样式里一直不移除 will-change: transform写在元素的默认样式里,意味着页面加载那一刻就给它创建合成层,而且永远不释放。单个元素影响不大,但列表页、表格这种元素多的场景,内存占用会迅速膨胀。
面试官还会怎么问?
怎么判断一个动画有没有走合成?
Chrome DevTools 打开 Rendering 面板,勾选 Paint Flashing。如果动画过程中元素闪绿色,说明在重绘;如果没有闪绿色,说明走的是合成。也可以打开 Layers 面板直接看元素是否在独立层上。
opacity 和 transform 为什么能跳过重排重绘?
因为这两个属性的变化不影响布局(不改变元素占的空间),也不影响绘制内容(元素的位图没变)。GPU 只需要在合成阶段对已有的纹理做矩阵变换或透明度混合,这些操作对 GPU 来说非常轻量。
will-change: auto 有什么用?
auto 是默认值,表示「不做任何提前提示」。显式写 will-change: auto 的用途是用 JS 动态移除提示——先设成 will-change: transform 触发提升,动画结束后设回 will-change: auto 释放合成层。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。