新型浏览器可以以低成本为以下两个 CSS 属性添加动画效果:transform
和 opacity
。如果您为任何其他内容添加动画,则很可能无法达到每秒 60 帧 (FPS) 的流畅速度。本文将说明出现这种情况的原因。
动画性能和帧速率
在 Web 上为任何内容添加动画时,普遍认为目标帧速率为 60 FPS。此帧速率可确保动画看起来流畅。在 Web 上,帧是指更新和重新绘制屏幕所需完成的所有工作所用的时间。如果每个帧未在 16.7 毫秒(1000 毫秒 / 60 ≈ 16.7)内完成,用户就会感受到延迟。
渲染管道
如需在网页上显示内容,浏览器必须按以下顺序执行以下步骤:
- 样式:计算应用于元素的样式。
- 布局:为每个元素生成几何图形和位置。
- 绘制:将每个元素的像素填充到图层中。
- 复合:将图层绘制到屏幕。
这四个步骤称为浏览器的渲染流水线。
当您在已加载的页面上为某个元素添加动画时,必须重新执行上述步骤。此过程从必须更改步骤才能播放动画开始。
如前所述,这些步骤是顺序的。例如,如果您为会更改布局的对象添加了动画,则绘制和合成步骤也必须重新运行。因此,为更改布局的对象添加动画的开销比为仅更改合成的对象添加动画的开销要高。
为布局属性添加动画效果
布局更改涉及计算受此更改影响的所有元素的几何形状(位置和大小)。如果您更改某个元素,则可能需要重新计算其他元素的几何图形。例如,如果您更改 <html>
元素的宽度,其任何子元素都可能会受到影响。由于元素溢出和相互影响的方式,树中更下方的更改有时会导致布局计算一直回溯到顶部。
可见元素的树越大,执行布局计算所需的时间就越长。
为绘制属性添加动画效果
绘制是确定应以何种顺序将元素绘制到屏幕上的过程。它往往是管道中运行时间最长的任务。
在现代浏览器中,大部分绘制都是在软件光栅化处理程序中完成的。根据应用中的元素如何分组到层中,除了已更改的元素外,可能还需要绘制其他元素。
为复合属性添加动画效果
合成是指将网页分解为图层,将有关网页应如何显示的信息转换为像素(光栅化),然后将图层组合在一起以创建网页(合成)的过程。
这就是为什么 opacity
属性会被列入成本低廉的动画效果列表中。只要此属性位于自己的图层中,GPU 便可以在合成步骤期间处理对其所做的更改。
基于 Chromium 的浏览器和 WebKit 会为 opacity
上具有 CSS 转换或动画的任何元素创建新层。
什么是图层?
通过将要进行动画处理或转换的项放置到新层上,浏览器只需重新绘制这些项,而无需重新绘制所有其他项。您可能熟悉 Photoshop 中的图层概念,其中包含一组可以一起移动的元素。 浏览器渲染层与此原理类似。
虽然浏览器在决定应在新层上添加哪些元素方面做得不错,但如果它缺少某个元素,还可以通过一些方法来强制创建层。如需了解详情,请参阅如何创建高性能动画。不过,创建新图层时应谨慎,因为每层都会使用内存。在内存有限的设备上,创建新层可能会导致更多性能问题,而不是解决您尝试解决的问题。此外,每一层的纹理都需要上传到 GPU。因此,您可能会遇到 CPU 和 GPU 之间的带宽限制。
CSS 与 JavaScript 的性能
您可能会想:从性能的角度来看,使用 CSS 还是 JavaScript 来制作动画更好?
基于 CSS 的动画以及 Web Animations(在支持该 API 的浏览器中)通常由一个名为合成器线程的线程处理。这不同于在其中执行样式、布局、绘制和 JavaScript 的浏览器主线程。 这意味着,如果浏览器正在主线程上运行一些高开销任务,则这些动画可以继续运行而不中断。
如本文所述,在许多情况下,变形和透明度的其他更改还可由合成器线程来处理。
如果任何动画触发绘制、布局或同时触发这两者,则主线程将必须执行工作。CSS 和 JavaScript 动画都是如此,并且布局或绘制的开销可能会使与 CSS 或 JavaScript 执行相关的工作量大打折扣,使问题变得无意义。