transform
transform 属于合成属性(composite property),对合成属性进行 transition/animation 动画将会创建一个合成层(composite layer),这使得被动画元素在一个独立的层中进行动画。通常情况下,浏览器会将一个层的内容先绘制进一个位图中,然后再作为纹理(texture)上传到 GPU,只要该层的内容不发生改变,就没必要进行重绘(repaint),浏览器会通过重新复合(recomposite)来形成一个新的帧。
margin top / left
top/left属于布局属性,该属性的变化会导致重排(reflow/relayout),所谓重排即指对这些节点以及受这些节点影响的其它节点,进行CSS计算->布局->重绘过程,浏览器需要为整个层进行重绘并重新上传到 GPU,造成了极大的性能开销。
小总结
对布局属性进行动画,浏览器需要为每一帧进行重绘并上传到 GPU 中
对合成属性进行动画,浏览器会为元素创建一个独立的复合层,当元素内容没有发生改变,该层就不会被重绘,浏览器会通过重新复合来创建动画帧
以下通过模拟测试在chrome上检测动画的FPS
左方的图片是通过改变元素top/left
进行动画的帧率,而右方则是调用translate
函数的帧率。我们可以明显看出左图的每一帧都执行了相对较长时间的paint,在每一帧内,cpu都需要计算该元素的其他样式,特别是相对需要复杂计算的盒阴影,渐变,圆角等样式,最后都需要将这些样式应用到该元素内。从这个角度看,如果对于较为老旧的移动设备进行相对复杂的动画,那么效果肯定不理想。
而通过调用translate
,会启动硬件加速,即在GPU层对该元素进行渲染。这样,GPU就会相对解放出来进行其他的计算,GPU对样式的计算相对较快,且保证较大的帧率。我们可以通过2d和3d的transform来启用GPU计算。
居中为什么用transform,而不是margin top/left
我们主要还是从浏览器渲染的性能方面考虑。浏览器渲染过程我们知道,浏览器中有JS引擎和渲染引擎,对于HTML页面的渲染就靠渲染引擎来完成。下面是chrome浏览器页面渲染的整体过程图:
Chrome渲染主要包括
- Parse Html----html解析
- Recalculate Style----查找并计算样式
- Layout-----排布
- Paint-----绘制
- Image Decode----图片解码
- Image Resize----图片大小绘制
- Composite Layers----合并图层并输出页面到屏幕
浏览器最终渲染出来的页面,跟Photoshop有点类似,是由多个图层合并而来。
补充transform的原理
transform是通过创建一个RenderLayers合成层,拥有独立的GraphicsLayers。每一个GraphicsLayers都有一个Graphics Context,其对应的RenderLayers会paint进Graphics Context中。合成器(Compositor)最终会负责将由Graphics Context输出的位图合并成最终屏幕展示的图案。满足如下条件的RenderLayers,会被认为是一个独立的合成层:有3D或者perspective transform的CSS属性的层video元素的层canvas元素的层flash对opacity和transform应用了CSS动画的层使用了CSS滤镜(filters)的层有合成层后代的层同合成层重叠,且在该合成层上面(z-index)渲染的层如果RenderLayer是一个合成层,那么它有属于它自己的单独的GraphicsLayer,否则它和它的最近的拥有GraphicsLayer的父layer共用一个GraphicsLayer。由此可见,transform发生在Composite Layer这一步,它所引起的paint也只是发生在单独的GraphicsLayer中,并不会引起整个页面的回流重绘。
层创立的条件如下:
- 3D 或透视变换 CSS 属性
- 使用加速视频解码的 元素
- 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 元素
- 复合插件(如 Flash)
- 进行 opacity/transform 动画的元素
- 拥有加速 CSS filters 的元素
- 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
GPU
我们经常会听到GPU会加速渲染,那GPU在这里又扮演什么角色呢?前面说到,合成器会负责将层合成绘制为最终的屏幕画面。在硬件加速体系结构,合成由GPU负责。在chrome浏览器多进程模型中,有一个专门的进程来负责传递Render进程的命令,即GPU进程。Render进程和GPU进程是通过共享内存传递的。Render进程可以快速的将命令发给命令缓冲区,并且返回到CPU密集的render活动中,留给GPU进程去处理这些命令。我们可以充分利用多内核机器上的GPU进程和CPU进程。这也是为什么GPU会加速渲染,使transform渲染速度更快的又一原因。4. margin top/leftmarign:外边距,定义元素周围的空间;简言之,可以改变元素的位移。在浏览器页面渲染的时候,margin可以控制元素的位置,也就是说,改变margin,就会改变render tree的结构,必定会引起页面layout回流和repaint重绘。因此,从浏览器性能考虑,transform会比margin更省时间。但是,transform真的处处适用吗?5. transform的局限性上面提到,transform实际上也是用到了GPU加速,也就是说占用了内存。由此可见创建GraphicsLayer,虽然洁身了layout,paint阶段,但Layer创建的越多,占用内存就会越大,而过多的渲染开销会超过性能的改善。因此,当且仅当需要的时候,才会为元素创建渲染层。
对元素进行动画的一些要点:
- 1,尽量使用keyframes和transform进行动画,这样浏览器会自身分配每帧的长度,并作出优化
- 2,如果非要使用js来进行动画,使用requestAnimateFrame
- 3,使用2d transform而不是改变top/left的值,这样会有更短的repaint时间和更圆滑的动画效果
- 4,移动端的动画效果可能会比pc端的差,因此一定要注意性能优化,尽量减少动画元素的DOM复杂性,待动画结束后异步执行DOM操作
参考:
https://juejin.im/post/5c32b0fb6fb9a049ac7950d9
http://www.cnblogs.com/accordion/p/4593576.html
https://www.zhihu.com/question/33629083
https://blog.csdn.net/qq_33988065/article/details/80371006