为什么CSS动画⽐JavaScript⾼效?

DOM树⽣成之后,还要经历布局、分层、绘制、合成、显⽰等阶段后才能显⽰出漂亮的⻚⾯。

显⽰器是怎么显⽰图像的

每个显⽰器都有固定的刷新频率,通常是60HZ,也就是每秒更新60张图⽚,更新的图⽚都来⾃于显卡中⼀ 个叫前缓冲区的地⽅,显⽰器所做的任务很简单,就是每秒固定读取60次前缓冲区中的图像,并将读取的图 像显⽰到显⽰器上。

显卡

显卡的职责就是合成新的图像,并将图像保存到后缓冲区中,⼀旦显卡把合成的图像写到后缓冲区,系统就 会让后缓冲区和前缓冲区互换,这样就能保证显⽰器能读取到最新显卡合成的图像


如何⽣成⼀帧图像

重排重绘合成三种⽅式。

通常渲染路径越⻓,⽣成图像花费的时间就越多。⽐如重排,它需要重新 根据CSSOM和DOM来计算布局树,这样⽣成⼀幅图⽚时,会让整个渲染流⽔线的每个阶段都执⾏⼀遍,如 果布局复杂的话,就很难保证渲染的效率了。⽽重绘因为没有了重新布局的阶段,操作效率稍微⾼点,但是 依然需要重新计算绘制信息,并触发绘制操作之后的⼀系列操作。

相较于重排和重绘,合成操作的路径就显得⾮常短了,并不需要触发布局和绘制两个阶段,如果采⽤了 GPU,那么合成的效率会⾮常⾼。


分层和合成

通常⻚⾯的组成是⾮常复杂的,有的⻚⾯⾥要实现⼀些复杂的动画效果,⽐如点击菜单时弹出菜单的动画特 效,滚动⿏标滚轮时⻚⾯滚动的动画效果,当然还有⼀些炫酷的3D动画特效。如果没有采⽤分层机制,从 布局树直接⽣成⽬标图⽚的话,那么每次⻚⾯有很⼩的变化时,都会触发重排或者重绘机制,这种“牵⼀发 ⽽动全⾝”的绘制策略会严重影响⻚⾯的渲染效率。

为了提升每帧的渲染效率,Chrome引⼊了分层和合成的机制

可以把⼀张⽹⻚想象成是由很多个图⽚叠加在⼀起的,每个图⽚就对应⼀个图层,Chrome合成器最终将 这些图层合成了⽤于显⽰⻚⾯的图⽚。

将素材分解为多个图层的操作就称为分层,最后将这些图层合并到⼀起的操作就称为合成


分层体现在⽣成布局树之后,渲染引擎会根据布局树的特点将其转换为层树 (Layer Tree),层树是渲染流⽔线后续流程的基础结构。

层树的每一个子节点对应的都是一个图层,接着的绘制阶段就在这些子节点上进行

绘制阶段其实就是生成一个绘制指令列表,真正的渲染阶段在光栅化阶段

光栅化阶段是真正的将图层进行合成并且会将页面分块,我们所看到的视图(viewport)是优先进行的,并且chorme采用首次合成图块的时候采用低分辨率的图片,可以大幅度加快渲染速度。

合成操作是在合成线程上完成的,这也就意味着在执⾏合成操作时,是不会影响到主线 程执⾏的。这就是为什么经常主线程卡住了,但是CSS动画依然能执⾏的原因


如何利⽤分层技术优化代码

对某个元素做⼏何形状变换、透明度变换或者⼀些缩放操作,如果使 ⽤JavaScript来写这些效果,会牵涉到整个渲染流⽔线,所以JavaScript的绘制效率会⾮常低下。

这时可以使⽤ will-change来告诉渲染引擎你会对该元素做⼀些特效变换,CSS代码如下:

.box {
will-change: transform, opacity;
}

这段代码就是提前告诉渲染引擎box元素将要做⼏何变换和透明度变换操作,这时候渲染引擎会将该元素单 独实现⼀帧,等这些变换发⽣时,渲染引擎会通过合成线程直接去处理变换,这些变换并没有涉及到主线程,这样就⼤⼤提升了渲染的效率。这也是CSS动画⽐JavaScript动画⾼效的原因