KAIROS.WORKSPACE
技术宇宙 / ONLINE
返回文章列表
前端工程advanced8 分钟

让动画跑在合成层:transform/opacity 与 layout thrash 的边界

前端动效性能的核心是只动 GPU 能独立处理的属性。讲清 transform/opacity 为何安全、哪些属性会触发布局重排、以及读写分离避免强制同步布局。

#性能#动效#渲染#CSS
排版
字号
行宽
目录 · 7 节

流畅动画的差别不在用了多少特效,而在每一帧浏览器被迫做多少工作。理解渲染管线,就能预判一个动画是 60fps 还是掉帧。

浏览器渲染四步

JS → Style(计算样式)→ Layout(几何位置)→ Paint(填色)→ Composite(合成上屏)

每帧重跑代价递减:Layout 比 Paint 贵,Paint 比 Composite 贵。理想动画只走最后一步——Composite,因为合成由 GPU 处理,主线程几乎不参与。

只有 transform 和 opacity 能“只合成”

改动 transform(translate/scale/rotate)和 opacity 时,元素已被提升为独立合成层,浏览器只需重新合成,跳过 Layout 与 Paint

.card-enter {
  transform: translateY(0.75rem);
  opacity: 0;
}
.card-enter-active {
  transform: translateY(0);
  opacity: 1;
  transition:
    transform 220ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 220ms;
}

而下面这些属性每变一次都会触发昂贵的重排或重绘:

动画属性 触发的最贵阶段
top/left/right/bottomwidth/heightmargin/padding Layout
box-shadowfilter: blur()background Paint
font-sizeline-height Layout

同样“上浮淡入”,用 top 位移是每帧 Layout + 父级重排,用 transform 是纯合成——这就是为什么动效规范里写“位移一律 translate,不用 top”。

会触发 Layout 的“隐形雷区”:JS 读几何

即使动画只用 transform,如果在 scroll/resize 回调里同步读取几何属性再立刻写入,会触发“强制同步布局”(layout thrash):

// 反面:每个元素读一次 offsetTop 写一次 style,读被写强制刷新布局 → N 次重排
items.forEach((el) => {
  el.style.transform = `translateY(${el.offsetTop}px)`;
});

读写分离:先批量读,再批量写:

const tops = items.map((el) => el.offsetTop); // 只读一次布局
requestAnimationFrame(() => {
  items.forEach((el, i) => {
    el.style.transform = `translateY(${tops[i]}px)`;
  }); // 集中写
});

高频事件(scroll)里还应加节流——用 requestAnimationFrame 把多次事件合并到一帧更新一次,而不是每个事件都读写。

will-change:用对是加速,用满是负债

will-change: transform 提前把元素提成合成层,动画开始前给一帧提示有益。但它有内存代价(每层独立纹理),滥用会让 GPU 内存吃紧、移动端反而更卡。原则:

  • 只在即将动画的元素上加,动画结束后移除;
  • 不要给大量列表项同时常驻 will-change
  • 揭示完成后释放:el.dataset.revealed && el.style.willChange = 'auto'

box-shadow / filter 的光晕怎么办

光晕动效常需 box-shadow 过渡,而它是 Paint 级。折中:

  • 静态光晕用 box-shadow(不变就不重绘);
  • 需要“发光呼吸”时用叠加一个 opacity 动画的伪元素层,把 Paint 转成合成——::after 画好阴影,只动它的 opacity

用 reduced-motion 兜底性能与体验

无论动画多便宜,都要尊重 prefers-reduced-motion

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

这既是无障碍要求(前庭功能敏感用户),也是低端设备的事实降载。

小结

动效性能的功课就三条:只动 transform/opacity、读写分离不制造强制同步布局、will-change 点到为止。把这三条内化成肌肉记忆,比事后拿性能面板逐帧救火省力得多。

Conversation

评论与互动

正在加载评论…

提交后需审核,不会立即公开。

Keep exploring

继续探索