css animation是实现web animation方法之一,其主要通过@keyframes和animation-*或者transition来实现一些web动效。不过今天我们聊的不是怎么制作web动画,咱们来聊聊css animation性能相关的话题。
浏览器渲染原理关于浏览器工作原理之前有一篇非常出名的文章《浏览器的工作原理:新式网络浏览器幕后揭秘》。文章详细阐述了浏览器工作原理,下面用两张图来分别描述firefox和chrome浏览器对web页面的渲染过程:
chrome渲染过程
有关于chrome浏览器渲染的详细内容,可以参考《图解浏览器渲染过程 – 基于webkit/blink内核chrome浏览器》一文。
firefox渲染过程
特别声明:接下来的内容都是针对于chrome浏览器进行讨论。
chrome渲染部分的实际含义从上面的流程图中不难看出,chrome渲染主要包括parse html、recalculate style、layout、rasterizer、paint、image decode、image resize和composite layers等。简单了解一下其含义,以便后续内容的更好理解。
parse html发送一个http请求,获取请求的内容,然后解析html的过程。
有一个经典的前端面试题:当你在浏览器中输入google.com并且按下回车之后发生了什么? 这个面试题或许能帮助大家更好的理解parse html,甚至是浏览器渲染的其他几个部分。
recalculate style重新计算样式,它计算的是style,和layout做的事情完全不同。layout计算的是一个元素绝对的位置和尺寸,或者说是“compute layout”。
recalculate被触发的时候做的事情就是处理javascript给元素设置的样式而已。recalculate style会计算render树(渲染树),然后从根节点开始进行页面渲染,将css附加到dom上的过程。
任何企图改变元素样式的操作都会触发recalculate。同layout一样,它也是在javascript执行完成后才触发的。
layout计算页面上的布局,即元素在文档中的位置及大小。正如前面所述,layout计算的是布局位置信息。任何有可能改变元素位置或大小的样式都会触发这个layout事件。
触发layout的属性非常的多,如果想了解什么属性会触发layout事件,可以在css triggers网站查阅。下图截了一部分:
rasterizer光栅化,一般的安卓手机都会进行光栅化,光栅主要是针对图形的一个栅格化过程。低端手机在这部分耗时还是蛮多的。
paint页面上显示东西有任何变动都会触发paint。包括拖动滚动条,鼠标选择中文字等这些完全不改变样式,只改变显示结果的动作都会触发paint。
paint的工作就是把文档中用户可见的那一部分展现给用户。paint是把layout和recalculate的计算的结果直接在浏览器视窗上绘制出来,它并不实现具体的元素计算。
image decode图片解码,将图片解析到浏览器上显示的过程。
image resize图片的大小设置,图片加载解析后,若发现图片大小并不是实际的大小(css改变了宽度),则需要resize。resize越大,耗时越久,所以尽量以图片的原始大小输出。
composite layers最后合并图层,输出页面到屏幕。浏览器在渲染过程中会将一些含有特殊样式的dom结构绘制于其他图层,有点类似于photoshop的图层概念。一张图片在potoshop是由多个图层组合而成,而浏览器最终显示的页面实际也是有多个图层构成的。
下面这些因素都会导致新图层的创建:
进行3d或者透视变换的css属性使用硬件加速视频解码的<video>元素具有3d(webgl)上下文或者硬件加速的2d上下文的<canvas>元素组合型插件(即flash)具有有css透明度动画或者使用动画式webkit变换的元素具有硬件加速的css滤镜的元素有关于composite方面的深入剖析,可以阅读《无线性能优化:composite》一文。
像素渲染流水线通过前面的介绍,在屏幕上最终呈现的页面,是类似于图层一样合并输出到屏幕上的。其实所写的web页面最终以像素的形式在浏览器屏幕上呈现。这样一来,我们需要理解所写的页面代码是如何被转换成屏幕上显示的像素。这个转换过程可以归纳为这样的一个流水线,主要包含五个关键步骤:
javascript:一般来说,我们会使用javascript来实现一些视觉变化的效果。比如css animation、transition和web animation api。style:计算样式。这个过程是根据css选择器,对每个dom元素匹配对应的css样式。这一步结束之后,就确定了每个dom元素上应该应用什么css样式规则。layout:布局。上一步确定了每个dom元素的样式规则,这一步就是具体计算每个dom元素最终在屏幕上显示的大小和位置。web页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。比如,<body>元素的width变化会影响其后代元素的宽度。因此,对于浏览器而言,布局过程是经常发生的。paint:绘制。本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个dom元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。composite:渲染层合并。前面也说过,对于页面中dom元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后在屏幕上呈现。对于有位置重叠的元素的页面,这个过程尤其重要,因为一量图层的合并顺序出错,将会导致元素显示异常。上述过程的每一步都有可能会发生,因此一定要弄清楚自己的代码将会运行在哪一步。
虽然在理论上,而面的每一帧都是结过上述的流水线处理之后渲染出来的,但并不意味着页面每一帧的渲染都需要经过上述五个步骤的处理。实际上,对视觉变化效果的一个帧的渲染,有三种常用的流水线。
javascript/css =>计算样式=>布局=>绘制=>渲染层合并
如果你修改一个dom元素的“layout”属性,也就是改变了元素的样式(比如width、height或者position等),那么浏览器会检查哪些元素需要重新布局,然后对页面激发一个reflow(重排)过程完成重新布局。被reflow(重排)的元素,接下来也会激发绘制过程,最后激发渲染层合并过程,生成最后的画面。
reflow又叫重排,是指浏览器计算页面的全部或部分布局所做的处理。reflow必定会引发重绘,这对于web的性能影响是极大的。
javascript/css => 计算样式 =>绘制 =>渲染层合并
如果你修改一个dom元素的“paint only”属性,比如背景图片、文字颜色或阴影等,这些属性不会影响页面的布局,因此浏览器会在完成样式计算之后,跳过布局过程,只会绘制和渲染层合并过程。
javascript/css => 计算样式 =>渲染层合并
如果你修改一个非样式且非绘制的css属性,那么浏览器会在完成样式计算之后,跳过布局和绘制的过程,直接做渲染层合并。这种方式在性能上是最理想的,对于动画和滚动这种负荷很重的渲染,我们要争取使用第三种渲染过程。
通过前面这么多的内容介绍,我们可以得知,影响web性能主要过程包括layout、paint和composite。那么对于css animation而言,我们的所有操作都是通过css的样式控制动画,言外之意,只要是会触发layout、paint和composite的css属性都会直接影响动画的性能。在css中所有影响layout、paint和composite的属性都可以通过css triggers**网站查阅。那么如何避免达到前面所述的,整个动画尽量避开重排和重绘,只做渲染层合并呢?暂且先不讨论,把这部分放到最后面来讨论。接下来接着先看看其他相关的知识点。
渲染性能在理解渲染性能之前,我们有必要先了解前面提到的两个概念重排(也就是回流)和重绘。因为这两者与前面介绍的像素渲染流水线中的layout和paint都有关系,而且layout和paint对性能的渲染又有莫大的关系。
reflow(重排)reflow(重排)指的是计算页面布局(layout)。某个节点reflow时会重新计算节点的尺寸和位置,而且还有可能触其后代节点reflow。在这之后再次触发一次repaint(重绘)
当render tree中的一部分(或全部)因为元素的尺寸、布局、隐藏等改变而需要重新构建。这就称为回流,每个页面至少需要一次回流,就是页面第一次加载的时候。
在web页面中,很多状况下会导致回流:
调整窗口大小改变字体增加或者移除样式表内容变化激活css伪类操作css属性javascript操作dom计算offsetwidth和offsetheight设置style属性的值css3 animation或transitionrepaint(重绘)repaint(重绘)或者redra