为什么设置动画 translateX 比 left 更流畅?
提问
被问到怎么设置一个从左到右的动画?我回答了设置 key-frame 的动画名,加上 from 和 to 去设置 left 属性的变化。
追问设置 left 是最好的选择吗?有更快更流畅的方案吗?我回答了 translateX。
然为什么 transform: translateX()
比 left 更流畅?我讲不出所以然来,只记得有这么个结论。
解答
查资料发现原来是设置 left 属性会频繁的造成浏览器回流重排,而 transform 和 opacity 属性不会,因为它是作为合成图层发送到 GPU 上,由显卡执行的渲染,这样做的优化如下
- 可以通过亚像素精度得到一个运行在特殊优化过的单位图形任务上的平滑动画,并且运行非常快。
- 动画不再绑定到 CPU 重排,而是通过 GPU 合成图像。 即使运行一个非常复杂的 JavaScript 任务,动画仍然会很快运行。
合成图层
所谓合成就是将页面的已绘制部分放在一起在屏幕上显示的过程。
我们举个例子,若以 left 作为动画的属性值,动画的过程是这样的,下面的例子和图片出自 这样使用 GPU 渲染 CSS 动画
<style>
#a, #b {
position: absolute;
}
#a {
left: 10px;
top: 10px;
z-index: 2;
animation: move 1s linear;
}
#b {
left: 50px;
top: 50px;
z-index: 1;
}
@keyframes move {
from { left: 30px; }
to { left: 100px; }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>
那么以 transform
作为动画的话
<style>
#a, #b {
position: absolute;
}
#a {
left: 10px;
top: 10px;
z-index: 2;
}
#b {
left: 50px;
top: 50px;
z-index: 1;
animation: move 1s linear;
}
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(70px); }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>
隐式合成:值得注意的一点是,上文 a 的 z-index 在 b 之上,而 b 却是在单独的合成层上,这种情况下浏览器会强制为元素 a 创建一个新的合成图层,并添加另一个重绘图,这种方式被成为隐式合成(请记住这种特殊的 GPU 合成模式并非 css 规范的一部分,知识浏览器内部的优化)。
事实上隐式合成非常频繁,浏览器将元素提升为合成层的原因有很多,包括
- 3D transforms: translate3d, translateZ等等;
<video>
,<canvas>
和<iframe>
元素;- СSS transitions 和 animations 而有 transform 动画和 opacity 属性的元素;
- position: fixed;
- will-change;
- filter;
结论
- 坚持使用 transform 和 opacity 属性更改来实现动画。
- 使用 will-change 或 translateZ 提升元素为合成层。
- 避免过度使用提升规则;各层都需要内存和管理开销。
事实上单独计算合成层的内存耗费是挺大的。你可以打开 safari 的控制台可以看到每个层的占用。
如图所见渲染这么一个 fixed 的区域,要 GPU 吃 1.69 MB的内存,如果层数多,渲染面积大,屏幕倍率高,那么要占用的内存就多。
并且我有个有意思的发现,在 mac retina 屏幕上看这个层占的 1.69 MB,而在一倍的外接显示屏上才占 700 kb。