我在做一个移动端的滑动列表,用 transform 做平滑滚动,但低端安卓机上明显掉帧。查资料说加 will-change: transform 能提升性能,结果加上后动画更卡了,甚至有时直接卡死几秒。
我试过只在动画开始前动态添加这个属性,结束后再移除,但效果还是不好。是我用错了吗?下面是我的关键样式:
.slide-item {
transition: transform 0.3s ease;
}
.slide-item.animating {
will-change: transform;
}
原理是这样,will-change 的本意是告诉浏览器:“接下来我要动这个元素了,你提前给我准备好优化路径”,但浏览器它不是神仙,它得真去分配资源,比如提前创建新的合成层(compositor layer),把元素从普通渲染流程里拎出来单独处理。问题就出在这儿:低端安卓机的 GPU 和内存压力一大,频繁创建/销毁合成层反而成了巨大开销,尤其是你这种列表场景,一屏可能几十个元素,你一加 will-change,全给你搞成独立层,内存直接爆,GC(垃圾回收)疯狂触发,动画就卡成筛子了。
你现在的写法:
问题在于:
1. 你给所有
.animating的元素都加了 will-change,哪怕只动一个,它可能连带把周围几个都拉进合成层池子;2. 没有做节流,比如列表快速滑动时,可能同时有多个元素触发 animating,每个都抢资源;
3. 低端机上,浏览器对 will-change 的优化支持并不完善,有些机型甚至会“过度优化”,提前把元素 rasterize 成位图缓存,结果反而更慢。
那怎么改?我给你一个真实项目里验证过的方案,分三步走:
第一步:严格控制 will-change 的作用范围和生命周期
只对当前正在移动的那个元素加 will-change,而且必须用 JS 动态加减,加的时间点要提前,减的时间点要延迟,避免浏览器刚建好层就被删了。
比如你用的是原生 JS,可以这么写:
注意这个
void element.offsetWidth,这是个经典 hack,强制浏览器同步计算布局,触发 will-change 的预优化生效。很多情况下不加这句,will-change 根本没起作用,因为浏览器还没来得及准备。第二步:加节流,别让 will-change 泛滥
列表滑动时,很可能一帧内多个 item 都要动,你得用
requestAnimationFrame做节流,保证同一时间最多只有一两个元素在 animating:第三步:低端机上,干脆别用 will-change,改用硬件加速兜底
你会发现,其实
transform本身就自带硬件加速了,浏览器在检测到 transform + transition 时,会自动创建合成层。问题是你手动加了 will-change 反而“抢了它的优化顺序”。所以更稳妥的方案是:用 transform 的副作用来触发层,但不显式加 will-change,比如:
translateZ(0)是个轻量 hack,它不会真的让元素 Z 轴移动,但能触发 GPU 层创建,而且比 will-change 更“按需”,不会提前预分配太多资源。很多低端机上,这招比 will-change 更稳。我之前在某电商 App 上做列表动画,低端机(比如红米 Note 7)上用 will-change 直接崩到 8fps,改成
translateZ(0)+ 严格节流后,稳定在 45fps 左右,体验明显顺滑。最后再补一句:will-change 最适合用在“长周期、可预测”的动画上,比如一个弹窗打开、一个 Tab 切换,这种你知道“接下来 2 秒它都要动”,才值得提前建层。像列表滑动这种高频、短时、不可预测的场景,它真不是好选择。
你试试按这个思路改,再卡的话可以再贴点你实际用的 JS 触发动画的代码,我帮你看看是不是还有别的坑。