GPU是如何合成图像的
GPU实际上可以看作一个独立的计算机,它有自己的处理器和存储器及数据处理模型。当浏览器向GPU发送消息的时候,就像向一个外部设备发送消息。
你可以把浏览器向GPU发送数据的过程,与使用ajax向服务器发送消息非常类似。想一下,你用ajax向服务器发送数据,服务器是不会直接接受浏览器的存储的信息的。你需要收集页面上的数据,把它们放进一个载体里面(例如JSON),然后发送数据到远程服务器。
同样的,浏览器向GPU发送数据也需要先创建一个载体;只不过GPU距离CPU很近,不会像远程服务器那样可能几千里那么远。但是对于远程服务器,2秒的延迟是可以接受的;但是对于GPU,几毫秒的延迟都会造成动画的卡顿。
浏览器向GPU发送的数据载体是什么样?这里给出一个简单的制作载体,并把它们发送到GPU的过程。
- 画每个复合层的图像
- 准备图层的数据
- 准备动画的着色器(如果需要)
- 向GPU发送数据
所以你可以看到,每次当你添加transform:translateZ(0)或will-change:transform给一个元素,你都会做同样的工作。重绘是非常消耗性能的,在这里它尤其缓慢。在大多数情况,浏览器不能增量重绘。它不得不重绘先前被复合层覆盖的区域。
隐式合成
一个或多个没有自己复合层的元素要出现在有复合层元素的上方,它就会拥有自己的复合层;这种情况被称为隐式合成。
浏览器将元素提升为一个复合层有很多种原因,下面列举了一些:
- 3d或透视变换css属性,例如
translate3d
,translateZ
等等(js一般通过这种方式,使元素获得复合层) <video><iframe><canvas><webgl>
等元素- 混合插件(如flash)
- 元素自身的
opacity
和transform
做 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-change
CSS属性,元素上有了这个属性,浏览器会提升这个元素成为一个复合层(不是总是)。这样动画就可以平滑的开始和结束。但是不要滥用这个属性,否则会大大增加内存消耗。
动画中只使用transform
和opacity
减小复合层的尺寸
对于图片,你要怎么做呢?你可以将图片的尺寸减少5%—10%,然后使用scale
将它们放大;用户不会看到什么区别,但是你可以减少大量的存储空间。
用css动画而不是js动画
css动画有一个重要的特性,它是完全工作在GPU上。因为你声明了一个动画如何开始和如何结束,浏览器会在动画开始前准备好所有需要的指令;并把它们发送给GPU。而如果使用js动画,浏览器必须计算每一帧的状态;为了保证平滑的动画,我们必须在浏览器主线程计算新状态;把它们发送给GPU至少60次每秒。除了计算和发送数据比css动画要慢,主线程的负载也会影响动画; 当主线程的计算任务过多时,会造成动画的延迟、卡顿。
所以尽可能地使用基于css的动画,不仅仅更快;也不会被大量的js计算所阻塞。
优化技巧总结
- 减少浏览器的重排和重绘的发生
- 不要使用table布局
- css动画中尽量只使用transform和opacity,这不会发生重排和重绘
- 尽可能地只使用css做动画
- 避免浏览器的隐式合成
- 改变复合层的尺寸