一篇文章说清浏览器解析和CSS(GPU)动画优化(转)

原文链接
英文原文
CSS Triggers

GPU是如何合成图像的

GPU实际上可以看作一个独立的计算机,它有自己的处理器和存储器及数据处理模型。当浏览器向GPU发送消息的时候,就像向一个外部设备发送消息。

你可以把浏览器向GPU发送数据的过程,与使用ajax向服务器发送消息非常类似。想一下,你用ajax向服务器发送数据,服务器是不会直接接受浏览器的存储的信息的。你需要收集页面上的数据,把它们放进一个载体里面(例如JSON),然后发送数据到远程服务器。

同样的,浏览器向GPU发送数据也需要先创建一个载体;只不过GPU距离CPU很近,不会像远程服务器那样可能几千里那么远。但是对于远程服务器,2秒的延迟是可以接受的;但是对于GPU,几毫秒的延迟都会造成动画的卡顿。

浏览器向GPU发送的数据载体是什么样?这里给出一个简单的制作载体,并把它们发送到GPU的过程。

  • 画每个复合层的图像
  • 准备图层的数据
  • 准备动画的着色器(如果需要)
  • 向GPU发送数据

所以你可以看到,每次当你添加transform:translateZ(0)或will-change:transform给一个元素,你都会做同样的工作。重绘是非常消耗性能的,在这里它尤其缓慢。在大多数情况,浏览器不能增量重绘。它不得不重绘先前被复合层覆盖的区域。

隐式合成

一个或多个没有自己复合层的元素要出现在有复合层元素的上方,它就会拥有自己的复合层;这种情况被称为隐式合成。

浏览器将元素提升为一个复合层有很多种原因,下面列举了一些:

  • 3d或透视变换css属性,例如translate3dtranslateZ等等(js一般通过这种方式,使元素获得复合层)
  • <video><iframe><canvas><webgl>等元素
  • 混合插件(如flash)
  • 元素自身的opacitytransform做 CSS 动画
  • 拥有css过滤器的元素
  • 使用will-change属性
  • position:fixed
  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

这看起来css动画的性能瓶颈是在重绘上,但是真实的问题是在内存上:

内存占用

使用GPU动画需要发送多张渲染层的图像给GPU,GPU也需要缓存它们以便于后续动画的使用。

一个渲染层,需要多少内存占用?为了便于理解,举一个简单的例子;一个宽、高都是300px的纯色图像需要多少内存?

300 * 300 * 4 = 360000字节,即360kb。这里乘以4是因为,每个像素需要四个字节计算机内存来描述。

假设我们做一个轮播图组件,轮播图有10张图片;为了实现图片间平滑过渡的交互;为每个图像添加了will-change:transform。这将提升图像为复合层,它将多需要19MB的空间。800 * 600 * 4 * 10 = 1920000

仅仅是一个轮播图组件就需要19MB的额外空间!

在chrome的开发者工具中打开Setting——>Experiments——>layers可以看到每个层的内存占用。

GPU动画的优点和缺点

现在我们可以总结一下GPU动画的优点和缺点:

  • 每秒60帧,动画平滑、流畅
  • 一个合适的动画工作在一个单独的线程,它不会被大量的js计算阻塞
  • 3D“变换”是便宜的

缺点:

  • 提升一个元素到复合层需要额外的重绘,有时这是慢的。(即我们得到的是一个全层重绘,而不是一个增量)
  • 绘图层必须传输到GPU。取决于层的数量和传输可能会非常缓慢。这可能让一个元素在中低档设备上闪烁。
  • 每个复合层都需要消耗额外的内存,过多的内存可能导致浏览器的崩溃。
  • 如果你不考虑隐式合成,而使用重绘;会导致额外的内存占用,并且浏览器崩溃的概率是非常高的。
  • 我们会有视觉假象,例如在Safari中的文本渲染,在某些情况下页面内容将消失或变形。

优化技巧

避免隐式合成

保持动画的对象的z-index尽可能的高。理想的,这些元素应该是body元素的直接子元素。当然,这不是总可能的。所以你可以克隆一个元素,把它放在body元素下仅仅是为了做动画。

将元素上设置will-changeCSS属性,元素上有了这个属性,浏览器会提升这个元素成为一个复合层(不是总是)。这样动画就可以平滑的开始和结束。但是不要滥用这个属性,否则会大大增加内存消耗。

动画中只使用transformopacity

减小复合层的尺寸

对于图片,你要怎么做呢?你可以将图片的尺寸减少5%—10%,然后使用scale将它们放大;用户不会看到什么区别,但是你可以减少大量的存储空间。

用css动画而不是js动画

css动画有一个重要的特性,它是完全工作在GPU上。因为你声明了一个动画如何开始和如何结束,浏览器会在动画开始前准备好所有需要的指令;并把它们发送给GPU。而如果使用js动画,浏览器必须计算每一帧的状态;为了保证平滑的动画,我们必须在浏览器主线程计算新状态;把它们发送给GPU至少60次每秒。除了计算和发送数据比css动画要慢,主线程的负载也会影响动画; 当主线程的计算任务过多时,会造成动画的延迟、卡顿。

所以尽可能地使用基于css的动画,不仅仅更快;也不会被大量的js计算所阻塞。

优化技巧总结

  • 减少浏览器的重排和重绘的发生
  • 不要使用table布局
  • css动画中尽量只使用transform和opacity,这不会发生重排和重绘
  • 尽可能地只使用css做动画
  • 避免浏览器的隐式合成
  • 改变复合层的尺寸