CSS动画原理及硬件加速
一、图层
图层即层叠上下文,具体概念和应用大家可以看我之前转自张鑫旭大神博客的《CSS层叠上下文和层叠顺序》,这里我们简单复习一下产生层叠上下文的原因。
1.根层叠上下文
指的是页面根元素,也就是滚动条的默认的始作俑者<html>
元素。这就是为什么,绝对定位元素在left
/top
等值定位的时候,如果没有其他定位元素限制,会相对浏览器窗口定位的原因。
2.定位元素与传统层叠上下文
对于包含有position:relative
/position:absolute
的定位元素,以及FireFox/IE浏览器(不包括Chrome等webkit内核浏览器)(目前,也就是2016年初是这样)下含有position:fixed
声明的定位元素,当其z-index
值不是auto
的时候,会创建层叠上下文。
3.CSS3与新时代的层叠上下文
以下情况会产生新的层叠上下文:
- 根元素(HTML)
- 绝对或相对定位且
z-index
值不为auto
- 一个伸缩项目
Flex Item
,且z-index
值不为auto
,即父元素display: flex|inline-flex
- 元素的
opacity
属性值小于 1
- 元素的
transform
属性值不为none
- 元素的
mix-blend-mode
属性值不为normal
- 元素的
filter
属性值不为normal
- 元素的
isolation
属性值为isolate
position: fixed
will-change
中指定了上述任意属性,即便你没有直接定义这些属性
- 元素的
-webkit-overflow-scrolling
属性值为touch
二、利用绝对定位+top/left实现动画
上图数据中的绿色条纹表示的就是使用 top
和 left
实现动画时浏览器发生的 repaint 操作,从中可以看出动画帧数远低于60
帧。
从 chrome 的开发者工具按 ESC
之后选择 “rendering” 面板,我们可以通过选中“Enable piant flashing”来进一步监测 repaint 操作。开启该选项后,页面中的 repaint 区域就会被绿色蒙版高亮显示出来。重新使用 top
和 left
的示例演示的话,你会发现包裹球的那块区域会一直闪烁绿色的蒙版。
按照常理来说,改变元素位置会产生重排,为什么上面图中显示的全是重绘呢?原因是绝对定位会建立一个新的图层,而此图层上只有当前一个元素,多以只会重绘,而不会重排。这也告诉我们,在同一层中,元素数量少的情况下,重排性能对更好,速度会更快。
三、transform2D实现动画
下图是使用CSS transform 检测到的数据:
如你所见,动画演示期间并没有过多的 repaint 操作。
那么,为什么 transform
没有触发 repaint 呢?简而言之,transform
动画由GPU控制,支持硬件加速,并不需要软件方面的渲染。
四、硬件加速原理
浏览器接收到页面文档后,会将文档中的标记语言解析为DOM树。DOM树和CSS结合后形成浏览器构建页面的渲染树。渲染树中包含了大量的渲染元素,每一个渲染元素会被分到一个图层中,每个图层又会被加载到GPU形成渲染纹理,而图层在GPU中transform
是不会触发 repaint 的,最终这些使用 transform
的图层都会由独立的合成器进程进行处理。
在我们的示例中,CSS transform
创建了一个新的复合图层,可以被GPU直接用来执行 transform
操作。在chrome开发者工具中开启“show layer borders”选项后,每个复合图层就会显示一条黄色的边界:
示例中的球就处于一个独立的复合图层,移动时的变化也是独立的:
此时,你也许会问:浏览器什么时候会创建一个独立的复合图层呢?事实上一般是在以下几种情况下:
- 3D 或者 CSS transform
<video>
和<canvas>
标签- CSS filters
- 元素覆盖时,比如使用了
z-index
属性
等一下,上面的示例使用的是 2D transition 而不是 3D 的 transforms 啊?这个说法没错,所以在timeline中我们可以看到:动画开始和结束的时候发生了两次 repaint 操作。
3D 和 2D transform 的区别就在于,浏览器在页面渲染前为3D动画创建独立的复合图层,而在运行期间为2D动画创建。动画开始时,生成新的复合图层并加载为GPU的纹理用于初始化 repaint。然后由GPU的复合器操纵整个动画的执行。最后当动画结束时,再次执行 repaint 操作删除复合图层。
五、强制GPU渲染
并不是所有的CSS属性都能触发GPU的硬件加速(图层在GPU中属性改变不会触发 repaint ),实际上只有少数属性可以,比如下面的这些:
transform
opacity
filter
为了避免 2D transform 动画在开始和结束时发生的 repaint 操作,我们可以硬编码一些样式来解决这个问题:
.example1 { transform: translateZ(0); } .example2 { transform: rotateZ(360deg); }
这段代码的作用就是让浏览器执行 3D transform。浏览器通过该样式创建了一个独立图层,图层中的动画则有GPU进行预处理并且触发了硬件加速。
如果某一个元素的背后是一个复杂元素,那么该元素的 repaint 操作就会耗费大量的资源,此时也可以使用上面的技巧来减少性能开销。
六、使用硬件加速的问题
使用硬件加速并不是十全十美的事情,比如:
- 内存。如果GPU加载了大量的纹理,那么很容易就会发生内容问题,这一点在移动端浏览器上尤为明显,所以,一定要牢记不要让页面的每个元素都使用硬件加速。
- 使用GPU渲染会影响字体的抗锯齿效果。这是因为GPU和CPU具有不同的渲染机制。即使最终硬件加速停止了,文本还是会在动画期间显示得很模糊。
七、总结
感觉这里面逻辑很乱,在这里整理一下。首先transform和绝对定位都会产生新的图层,所以都不存在重排,图层在GPU中transform又不会引起重绘,这就是硬件加速的原理。另外,transform3D和2D的区别在于3D渲染前便会产生新的图层,而2D是在运行时产生图层,运行结束时删除图层。
注:本文在整理了南北在W3C上写的《CSS动画之硬件加速》中的知识点,并加入了自己的理解和总结