学完这篇你能得到:一套可落地的判断标准——知道为什么动 left/top 会卡、什么时候该换 transform、以及怎么用 DevTools 验证优化真的生效。
一、先搞懂浏览器渲染一帧都干了什么
浏览器渲染一帧的流水线大致是四步:
- 计算样式(Style)
- 布局(Layout / Reflow)——算出每个元素的位置和尺寸
- 绘制(Paint)——把元素画成像素
- 合成(Composite)——图层合并上屏
关键点在于:Layout 和 Paint 的代价随 DOM 规模增长,改一个元素可能连累一片;Composite 只是把已经画好的图层重新摆个位置,代价基本恒定。
left / top 属于布局属性,改它浏览器必须重跑 Layout。而 transform / opacity 属于「只需合成」的属性,能跳过前两步。
二、写一个能看出差距的例子
.bad {
position: absolute;
left: 0;
transition: left .3s;
}
.bad:hover { left: 300px; }
.good {
transition: transform .3s;
}
.good:hover { transform: translateX(300px); }
注意:transform 的位移是「视觉位移」,元素在文档流里的实际占位不变。如果改动后周围元素没按预期让位,那不是 bug,是 transform 本来就不参与布局——这也正是它便宜的原因。
三、用 DevTools 确认瓶颈在哪
- 打开 Chrome DevTools → Performance 面板
- 点录制,手动做一次动画,然后停止
- 看 Main 轨道有没有大块的紫色 Layout 和绿色 Paint
如果每帧都出现 Layout,说明你在动布局属性;只剩 Composite 就对了。
注意:不要凭感觉优化。手机上的卡顿经常是主线程被 JS 占住,跟 CSS 无关,先看火焰图再动手。
四、把 left/top 换成 transform 的实操步骤
第一步,找出所有在 transition / animation / JS 里被改的 left、top、right、bottom、width、height、margin。
第二步,改成 transform: translate() 或 translate3d(),位移量用 px 或百分比。
第三步,如果同一个页面上有多个元素要做动画,提前分层:
.card {
will-change: transform;
transition: transform .3s ease-out;
}
.card:hover { transform: translateY(-6px); }
第四步,动画结束或组件卸载后移除 will-change,别让它长期占着显存。
注意:will-change 不是越多越好。每个提升的图层都要单独占内存,几十个图层反而掉帧,还会让文字发虚。只在动画即将发生前加,结束后撤。
五、几个常见的坑
- 用 scale 代替 width/height 做尺寸动画时,内部文字和图片会被一起拉伸。要保持内容不变形,可对子元素做等比例反向 scale。
- transform 会创建新的包含块,里面的 position: fixed 元素会以这个元素为参照,不再是视口。
- getBoundingClientRect() 返回的是变换后的视觉位置。用 transform 位移之后拿它做碰撞检测会算错。
- 列表重排这类布局动画没法靠单个 transform 解决,用 FLIP:先读旧位置、改 DOM、读新位置、用 transform 反向位移,再过渡回 0。
小结
- 渲染流水线中 Layout / Paint 最贵,Composite 最便宜,目标是让动画只走 Composite。
- left / top / width / height 触发 Layout;transform / opacity 只触发 Composite。
- 先用 Performance 面板定位,再改代码,最后再验证一次。
- will-change 用完就撤,图层不是越多越好。
- transform 不影响文档流、会创建包含块、还会影响 getBoundingClientRect,这三条记牢。