CSS3动画给Web体验带来巨大提升,但创建高性能动画却不那么容易。你可能发现你的动画不太流畅(特别是在移动端),本文将探讨CSS3动画性能及其原理。
浏览器渲染过程
浏览器的渲染过程就是将页面转换成像素显示到屏幕上,它包括如下步骤:
对于CSS3动画来说,每一帧都要经历上述过程。关于最后一步合并渲染层(可以类比Photoshop的图层),浏览器会在特定的场合创建独立的渲染层,每个渲染层由GPU独立绘制,互不影响,最后浏览器再把各个渲染层合并。这是一种代价较低的操作。
人眼感受流畅的动画跑在60FPS左右,也就是说,每一帧要在(1S/60 = 16.66ms)16毫秒之内完成。如果你的动画触发了布局,那就意味着将有大量的元素需要重新绘制,浏览器渲染的时间很可能超过16ms,页面就会出现卡顿。然而在移动端仅仅重绘也很慢。所以要创建高性能动画,我们就要设法跳过第2步和第3步:
使用transform和opacity
为了跳过布局和绘制,你只能使用那些仅触发渲染层合并的属性。目前,只有两个属性是满足这个条件的:transform和opacity。
应用transform或opacity属性的的元素将独占一个渲染层。它们的绘制在单独的层中由GPU处理,最后由浏览器合并渲染层,因此不会触发重绘。这个过程也被成为硬件加速。
让我们先用top和left属性创建一个动画(这里省略了兼容性写法):
效果
为了在桌面浏览器上看出卡顿效果,这里将多个同样的动画元素叠在一起,可以看到明显的卡顿。
现在,我们用Chrome DevTools来看看发生了什么。打开Timeline面板并勾选Paint,便可以对动画进行采样分析了。
绿色的方块代表Paint操作,可以看到每一帧都发生了重绘:
红色的三角表示发生了jank,即一帧的渲染时间大大超过了16ms,动画就会发生卡顿。
现在,将动画用tranform代替:
效果
动画变得流畅了。
再次采样分析,可以看到,没有发生Paint,而且每一帧的时间都接近16.66ms:
要查看创建的渲染层,点击横条上的帧,就会出现Layers选项卡,通过它你可以查看所有渲染层和它们被创建的原因。
强制创建渲染层
除了利用transform或opacity,你还可以主动把元素提升到渲染层中(强制硬件加速):
.ball{will-change: transform;}
如果浏览器不支持will-change属性的话,利用3D动画属性:
.ball{transform:translateZ(0)/*或者*/transform:translate3D(0, 0, 0)}
这个属性告诉浏览器元素将执行动画,于是浏览器会预先为元素创建独立的渲染层。它和2Dtransform的区别是,2Dtransform只是在动画执行的时候创建渲染层,待动画结束后删除渲染层,而这里是预先创建渲染层。
我们加上这个属性,并改变元素的left和top属性,看看会发生什么:
效果
现在,动画效果比不加translateZ(0)要好,但不如transform流畅。
在Layer标签可以看到,浏览器创建了独立渲染层。并且,没有触发Paint。
但是,在某些时间段仍然发生了junk(红色区域),动画效果并不能像transform一样“丝般顺滑”。至于junk的原因,可能是渲染过程中的一些额外工作导致的(例如渲染层的管理,CPU和GPU的通信等)。
预先创建渲染层会增大硬件负担,这会带来一些副作用,所以应当谨慎使用这个属性(进行实际测试),以免带来的副作用大于好处。
PS:强制创建渲染层后,可能会导致某些动画闪烁的问题,笔者在安卓微信中遇到过,根本原因尚不明。可以尝试以下代码fix。
.ball{-webkit-backface-visibility: hidden;-webkit-perspective:1000;}
总结
仅使用transform和opacity创建动画
谨慎使用will-change: transform或transform: translateZ(0)手动创建渲染层