现代浏览器可以低成本地为两个 CSS 属性添加动画效果:transform 和 opacity。如果您为其他任何内容添加动画效果,则很可能无法达到流畅的 60 帧/秒 (FPS)。这篇帖子介绍了为什么会这样。
动画性能和帧速率
人们普遍认为,在网页上为任何内容添加动画效果时,目标帧速率应为 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 执行相关联的任何工作,从而使该问题变得毫无意义。