现代浏览器能够以较低的成本为两个 CSS 属性添加动画效果:transform
和 opacity
。如果您添加任何其他动画效果,可能无法达到 60 帧/秒 (FPS) 如丝般顺滑的流畅性。这篇博文介绍了出现这种情况的原因。
动画性能和帧速率
为 Web 上的任何内容添加动画效果时,人们普遍认为,目标帧速率为 60 FPS。此帧速率将确保您的动画看起来流畅。在网络上,帧是指完成更新和重新绘制屏幕所需的所有工作所需的时间。 如果每一帧未在 16.7 毫秒(1000 毫秒 / 60 ~ 16.7)内完成,用户就会感觉到延迟。
渲染管道
要在网页上显示内容,浏览器必须执行以下依序步骤:
- 样式:计算应用于元素的样式。
- 布局:为每个元素生成几何图形和位置。
- 绘制:将每个元素的像素填充到层中。
- Composite:将图层绘制到屏幕上。
这四个步骤称为浏览器的渲染管道。
当您为已加载网页上的内容添加动画效果时,必须重复执行这些步骤。此过程从为了播放动画而必须更改的步骤开始。
如前所述,这些步骤是按顺序执行的。例如,如果您为会更改布局的内容添加动画效果,那么绘制和合成步骤也必须再次运行。因此,为会更改布局的内容添加动画效果比为仅更改合成的内容设置动画的开销更高。
为布局属性添加动画效果
布局更改涉及计算受此更改影响的所有元素的几何图形(位置和大小)。如果您更改了某个元素,则可能需要重新计算其他元素的几何图形。例如,如果您更改 <html>
元素的宽度,则它的任何子元素都可能会受到影响。由于元素溢出和相互影响的方式,在树中再向下的更改有时可能会导致布局计算回到顶端。
可见元素树越大,执行布局计算所需的时间就越长。
为绘制属性添加动画效果
绘制是确定应按何种顺序将元素绘制到屏幕上的过程。它通常是流水线中运行时间最长的任务。
在现代浏览器中,大多数绘制都是在软件光栅化工具中完成的。根据将应用中的元素分组为层的方式,除了已更改的元素之外,可能还需要对其他元素进行绘制。
为复合属性添加动画效果
合成是将页面拆分为多个图层、将有关网页外观的信息转换为像素(光栅化)以及将图层组合在一起以创建页面(合成)的过程。
因此,opacity
属性包含在制作动画开销很低的内容列表中。只要此属性位于自己的层中,GPU 就可以在合成步骤中处理对其的更改。基于 Chromium 的浏览器和 WebKit 会为在 opacity
上具有 CSS 过渡或动画的任何元素创建一个新层。
什么是图层?
通过将具有动画效果或过渡到新图层的内容放置到新图层上,浏览器只需要重新绘制这些内容,而无需重新绘制其他内容。您可能比较熟悉 Photoshop 的“图层”概念,图层包含一系列可以一起移动的元素。浏览器渲染层与此类似。
虽然浏览器可以很好地决定应在新层上添加哪些元素,但如果缺少某个元素,系统仍会强制创建层。您可以在如何创建高性能动画中找到这方面的信息。 不过,创建新层时应格外小心,因为每层都会使用内存。在内存有限的设备上,创建新层可能会导致比您试图解决的性能问题更多。 此外,每个图层的纹理都需要上传到 GPU。因此,您可能会达到 CPU 和 GPU 之间的带宽限制。
CSS 与 JavaScript 的性能对比
您可能会好奇:从性能的角度来看,为动画使用 CSS 还是 JavaScript 更好吗?
基于 CSS 的动画和网页动画(在支持该 API 的浏览器中)通常在称为“合成器线程”的线程上处理。这与浏览器的主线程不同,主线程在主线程中执行样式、布局、绘制和 JavaScript 操作。 这意味着,如果浏览器正在主线程上运行一些开销很大的任务,则这些动画可以继续播放而不会中断。
如本文所述,在许多情况下,对变形和不透明度进行的其他更改也可由合成器线程处理。
如果任何动画触发了绘制和/或布局,则需要主线程执行工作。 这一点对 CSS 和 JavaScript 动画而言都是如此,布局或绘制的开销可能会使与 CSS 或 JavaScript 执行相关的任何工作变得轻而易举,使问题变得毫无意义。