使用 will-change 提升动画性能,为什么反而更卡了?
我在做一个移动端的下拉刷新动画,听说加 will-change: transform 能提升性能,就给元素加上了。但实际测试发现,动画反而变得更卡顿了,这是为啥?
我是在 touchmove 时动态设置 will-change 的,代码大概是这样:
element.addEventListener('touchstart', () => {
element.style.willChange = 'transform';
});
element.addEventListener('touchend', () => {
element.style.willChange = 'auto';
});
是不是用法有问题?还是说在某些机型上适得其反?
首先,动态设置 will-change 的时机不对。在 touchstart 和 touchend 之间频繁地更改样式属性本身就可能导致重排和重绘,尤其是在移动设备上,渲染资源本来就比较紧张。所以,你可以在 touchstart 的时候设置 will-change,但在 touchend 的时候并不需要马上取消它,可以等到动画结束或者某个状态确认之后再恢复到 auto。
其次,will-change 本身是一个非常强大的工具,但也是有代价的。浏览器会为设置了 will-change 的元素分配更多的内存来提前进行渲染优化,这在某些低端设备上可能会导致性能下降,因为它限制了其他元素的渲染资源。因此,尽量避免过度使用或者长时间保持 will-change 在高开销的状态。
最后,对于下拉刷新这种场景,通常可以考虑使用 CSS 动画或者 Web Animations API 来代替直接操作 style 属性,这样可以更好地利用浏览器的优化机制。
基于以上几点,你可以尝试以下改进方案:
1. 延迟取消 will-change:
你可以在 touchend 之后,等到动画完成之后再取消 will-change,而不是立即取消。
2. 使用 CSS 动画替代直接操作 style:
如果你的动画逻辑不复杂,可以尝试定义一个 CSS 类来控制动画效果,这样可以更好地利用浏览器的优化机制。
然后在 CSS 中定义 .animate-transform 类:
3. 监听动画结束事件:
可以监听 CSS 动画或者 Web Animations API 的结束事件,确保在动画真正结束后才恢复 will-change 的默认值。
需要注意的是,这些方法都需要根据具体的实现情况进行调整。如果问题依然存在,可能还需要进一步分析具体的渲染瓶颈,或者考虑使用其他的优化策略,比如减少 DOM 操作、使用 requestAnimationFrame 等。
will-change本身不是魔法,它是有成本的。你现在的写法最大的问题在于触发的时机。当你在
touchstart事件里动态设置will-change时,浏览器需要在用户手指按下的那一瞬间,立刻为这个元素建立一个新的合成层,并分配 GPU 资源。这个建立过程本身就是耗时的,正好卡在了动画开始的第一帧,所以你会感觉到明显的掉帧。这就好比我们后端处理高并发请求,不能等请求进来了才去建立数据库连接池,那样肯定超时。正确的做法是提前预热。
另外,频繁地开启和关闭
will-change(你在touchstart开,touchend关),会导致浏览器频繁地创建和销毁图层,增加了垃圾回收(GC)的压力,这在低端机上更明显。针对下拉刷新这种场景,我有两个建议:
第一,如果这个元素确定要频繁动画,直接在 CSS 里静态写死
will-change: transform,或者用老派的transform: translateZ(0)强制开启硬件加速,别用 JS 动态去切。第二,如果非要用 JS 动态控制,一定要在动画开始之前就把
will-change加上,给浏览器一点喘息和准备的时间,别卡在touchstart这种高频事件里。给你改个写法参考一下,直接在 CSS 里搞定最省事:
这样浏览器在页面加载渲染的时候就会把层建好,
touchmove的时候直接用 GPU 跑,丝般顺滑。别为了省那点显存而在 JS 里折腾,得不偿失。