will-change CSS 指南

61 阅读4分钟

1. 定义:will-change 是什么?

will-change 是一个 CSS 属性,它允许你提前“告知”浏览器,某个元素上的特定属性即将发生变化

可以把它想象成一个给浏览器的“性能提示”。当你使用这个属性时,浏览器就可以提前为这个元素的动画或变换做好优化准备,例如为其创建一个独立的合成层(Compositing Layer),从而在动画实际发生时,能够更流畅、更高效地进行渲染。

2. 技术原理:它如何提升性能?

为了理解 will-change 的作用,我们需要先了解浏览器的基本渲染流程:

  1. 布局 (Layout/Reflow) : 计算元素在屏幕上的位置和大小。
  2. 绘制 (Paint) : 填充像素,绘制元素的颜色、边框等视觉表现。
  3. 合成 (Composite) : 将所有绘制好的“图层”(Layers)按正确的顺序合并,最终显示在屏幕上。

大多数 CSS 属性(如 width, height, margin)的改变都会触发耗费性能的“布局”和“绘制”过程。然而,transformopacity 这两个属性非常特殊,改变它们通常只需要执行“合成”这一步,这个过程可以被 GPU (显卡) 加速,因此性能极高。

will-change 的核心作用就是,当你提示浏览器某个元素的 transformopacity 即将改变时,浏览器会倾向于提前将该元素提升到一个独立的合成层。这样,当动画发生时,浏览器只需让 GPU 去移动或改变这个独立图层的透明度,而无需重新布局和绘制,从而避免了主线程的阻塞,实现了丝滑的动画效果。

3. 语法与参数详解

will-change 接受以下几种类型的参数值:

参数值作用
auto默认值。不进行任何特别的优化。
<custom-ident>最常用的值。直接填入一个或多个你计划用动画改变的 CSS 属性名,用逗号隔开。
transform: 用于 2D/3D 变换(移动、缩放、旋转)。will-change 的最佳应用场景
opacity: 用于改变透明度。同样是 will-change 的理想应用场景。
left, top 等:也可以使用,但这些属性本身会触发重排,优化效果不如 transform
scroll-position专门用于优化滚动。提示浏览器该元素会因为滚动而频繁改变位置。
contents提示浏览器该元素的内部内容即将发生变化。

4. 如何与动画“绑定”?

一个常见的误解是 will-change 会绑定到某个动画名称。实际上,它不绑定动画本身,而是绑定到动画所操作的 CSS 属性上

它们的关系如下:

  1. 你决定要对一个元素的 transform 属性做动画。
  2. 你使用 will-change: transform; 提前准备
  3. 浏览器收到提示,进行优化(如创建新图层)。
  4. 你使用 transitionanimation 定义动画
  5. transform 的值被某个事件(如 :hover触发改变时,流畅的动画便会执行。

5. 代码示例

示例 1:与 transition 配合的悬停效果

/* 基础样式和过渡效果定义 */
.card {
  transition: transform 0.3s ease-out;
}

/* 最佳实践:只在即将动画时添加 will-change */
.card:hover {
  will-change: transform;
  transform: translateY(-10px);
}

示例 2:与 @keyframes 动画配合

/* 定义动画 */
@keyframes slide-in {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* 对即将应用动画的元素给出提示 */
.panel.is-entering {
  /* 提示浏览器,transform 和 opacity 两个属性都将发生变化 */
  will-change: transform, opacity;
  animation: slide-in 0.5s forwards;
}

示例 3:使用 JavaScript 精确控制 (最推荐)

在实际项目中,通过 JavaScript 在动画开始前添加、结束后移除 will-change 是最理想的方式。

const element = document.querySelector('.my-element');

element.addEventListener('mouseenter', () => {
  // 动画开始前,添加 will-change
  element.style.willChange = 'transform';
});

element.addEventListener('animationend', () => {
  // 动画结束后,移除 will-change 以释放资源
  element.style.willChange = 'auto';
});

6. 使用要点与“陷阱”(重要!)

will-change 是一把双刃剑,滥用它比不用它更糟糕。请务必遵守以下原则:

  • 不要滥用

    绝对不要把它应用到大量元素上。每创建一个合成层都会消耗额外的内存和GPU资源。如果你让所有元素都“准备好”,不仅会浪费大量资源,也等于没有给出任何有效提示。

    绝对禁止的做法

    * {
      will-change: transform; /* 灾难性的性能杀手!*/
    }
    
  • 在恰当的时机使用

    will-change 的设计初衷是作为一个临时性的优化。最佳实践是在动画或交互即将开始时添加它,在结束后立即移除它。让它永久挂在一个元素上,意味着让这个元素永久地占用额外的内存资源。

  • 给浏览器一点时间

    will-change 是一个“提示”,浏览器收到后需要一点时间来完成优化设置。如果你在改变属性的同一帧才添加 will-change,可能就太晚了,起不到任何优化效果。这就是为什么最好在交互发生前(如 mouseenter 时,而不是 :active 时)就添加它。

7. 官方文档