为什么有些动画速度很慢?

新型浏览器可以以低成本为以下两个 CSS 属性添加动画效果:transformopacity。如果您为其他任何内容添加动画,则很可能无法达到每秒 60 帧 (FPS) 的流畅帧速率。本文将说明出现这种情况的原因。

动画性能和帧速率

在 Web 上为任何内容添加动画时,普遍认为目标帧速率为 60 FPS。此帧速率可确保动画看起来流畅。在 Web 上,帧是指更新和重新绘制屏幕所需完成的所有工作所用的时间。如果每个帧未在 16.7 毫秒(1000 毫秒 / 60 ≈ 16.7)内完成,用户就会感受到延迟。

渲染管道

如需在网页上显示内容,浏览器必须按以下顺序执行以下步骤:

  1. 样式:计算应用于元素的样式。
  2. 布局:为每个元素生成几何图形和位置。
  3. 绘制:填充每个元素的像素。
  4. 复合:将元素分隔为图层,并将图层绘制到屏幕上。

这四个步骤称为浏览器的渲染流水线

当您在已加载的页面上为某个元素添加动画时,必须再次执行上述步骤。此流程从必须更改才能让动画发生的步骤开始。

如前所述,这些步骤是顺序的。例如,如果您为会更改布局的对象添加动画,则绘制和合成步骤也必须重新运行。因此,为更改布局的对象添加动画的开销高于为仅更改合成的对象添加动画的开销。

为布局属性添加动画效果

布局更改涉及计算受更改影响的所有元素的几何图形(位置和大小)。如果您更改某个元素,则可能需要重新计算其他元素的几何图形。例如,如果您更改 <html> 元素的宽度,其任何子元素都可能会受到影响。由于元素溢出和相互影响的方式,树结构更深处的更改有时会导致布局计算一直回溯到顶部。

可见元素树越大,执行布局计算所需的时间就越长。

为绘制属性添加动画效果

绘制是确定应以何种顺序将元素绘制到屏幕的过程。它通常是流水线中运行时间最长的任务。

现代浏览器中的大部分绘制操作都是在软件光栅化程序中完成的。根据应用中的元素如何分组为图层,除了发生更改的元素之外,可能还需要绘制其他元素。

为复合属性添加动画效果

合成是指将网页分解为图层,将有关网页应如何显示的信息转换为像素(光栅化),然后将图层组合在一起以创建网页(合成)的过程。

这就是为什么 opacity 属性会被列入成本低廉的动画效果列表中。只要此属性位于自己的图层中,GPU 便可以在合成步骤期间处理对其所做的更改。 基于 Chromium 的浏览器和 WebKit 会为 opacity 上具有 CSS 转换或动画的任何元素创建新层。

什么是图层?

通过将要进行动画处理或转换的内容放置到新层上,浏览器只需重新绘制这些内容,而无需重新绘制所有其他内容。您可能熟悉 Photoshop 中的图层概念,其中包含一组可以一起移动的元素。 浏览器渲染层与此类似。

虽然浏览器在决定应该在新图层上显示哪些元素方面做得很好,但如果它漏掉了某个元素,您可以通过一些方法强制创建图层。如需了解详情,请参阅如何制作高性能动画。 不过,创建新图层时应谨慎,因为每个图层都会使用内存。在内存有限的设备上,创建新层可能会导致更多性能问题,而不是您尝试解决的问题。此外,每个图层的纹理都需要上传到 GPU。因此,您可能会遇到 CPU 和 GPU 之间的带宽限制。

CSS 对比 JavaScript 的性能

您可能会想:从性能角度来看,使用 CSS 还是 JavaScript 来实现动画效果更好?

基于 CSS 的动画和 Web 动画(在支持该 API 的浏览器中)通常在称为“合成器线程”的线程中处理。这与浏览器的主线程不同,后者用于执行样式设置、布局、绘制和 JavaScript。这意味着,如果浏览器在主线程上运行一些开销较大的任务,这些动画可以继续运行,而不会被中断。

正如本文所述,在许多情况下,合成器线程还可以处理对转换和不透明度的其他更改。

如果任何动画触发绘制或布局(或两者兼有),则主线程将需要执行工作。这对 CSS 和 JavaScript 动画都适用,并且布局或绘制开销很可能比与 CSS 或 JavaScript 执行相关的任何工作都小,因此这个问题没有意义。