避免绘制对于实现流畅的帧速率至关重要,尤其是在移动设备上。不过,有时油漆会出现在最不寻常的地方。本文将探讨动画 GIF 为何会导致不必要的绘制操作,以及您可以采用的简单修复方法。
层次叠加的迷人魅力
您可能已经知道,现代浏览器可能会将一组 DOM 元素绘制为单独的“图片”(称为层)。有时,整个网页只有一层,有时有数百层,在极少数情况下,甚至有数千层!
当 DOM 元素组合到一个图层中,并且其中一个元素在视觉上发生变化时,我们最终不仅要绘制发生变化的元素,还要绘制图层中与发生变化的元素重叠的所有其他元素。在某个对象上绘制另一个对象会导致被覆盖的像素实际上永远“丢失”;如果您想恢复原始像素,则需要重新绘制它们。
因此,有时我们希望将某个元素与其他元素隔离,以便在绘制该元素时,我们无需重新绘制未更改的其他元素。例如,当您将固定页面标题与可滚动内容组合使用时,每次内容滚动时,您都必须重新绘制标题以及新显示的内容。通过将标题放置在单独的层中,浏览器可以优化滚动。当您滚动时,浏览器可以移动层(可能需要借助 GPU),从而避免重新绘制任一层。
每增加一层都会增加内存用量并增加性能开销,因此目标是在保持良好性能的同时,将网页分组为尽可能少的层。
这一切与 GIF 动画有什么关系?
我们来看看这张图片:

这是简单应用的可能层级设置。这里有四个层级:其中三个(第 2 层到第 4 层)是界面元素;后面的层级是加载器,恰好是一个 GIF 动画。在正常流程中,您会在应用加载时显示加载器(第 1 层),然后在所有内容都完成后显示其他层。但关键在于:您需要隐藏 GIF 动画。
但为什么我需要隐藏它?
问得好。在理想情况下,浏览器只需为您检查 GIF 的可见性,并避免自动绘制。遗憾的是,检查动态 GIF 在屏幕上是否被遮挡或可见通常比直接绘制它更耗费资源,因此系统会绘制它。
在理想情况下,GIF 位于自己的图层中,浏览器只需绘制并将其上传到 GPU。但在最糟糕的情况下,您的所有元素都可能会被分组到单个图层中,并且浏览器必须重新绘制每个元素。完成后,它仍然需要将所有内容上传到 GPU。所有这些工作都会针对每个 GIF 帧执行,即使用户根本看不到 GIF 也是如此!
在桌面设备上,您可能可以采用这种绘制行为,因为 CPU 和 GPU 的性能更强,并且有足够的带宽用于在两者之间传输数据。不过,在移动设备上,绘制非常耗费资源,因此您必须非常小心。
哪些浏览器会受到影响?
通常情况下,不同浏览器的行为会有所不同。目前,Chrome、Safari 和 Opera 都会重新绘制,即使 GIF 被遮挡也是如此。另一方面,Firefox 会发现 GIF 被遮挡,因此无需重新绘制。Internet Explorer 仍然是一个黑盒子,即使在 IE11 中,由于 F12 工具仍处于开发阶段,因此无法指明是否正在进行任何重绘。
如何判断我是否遇到了此问题?
最简单的方法是在 Chrome 开发者工具中使用“显示绘制矩形”。加载 DevTools,然后按右下角的齿轮图标 (),选择 Rendering 部分下的 Show paint rectangles。

现在,您只需查找如下所示的红色矩形:

屏幕上的小红框表示 Chrome 正在重新绘制某些内容。您知道,在其他元素后面隐藏着一个加载器 GIF,因此当您看到这样的红色框时,需要隐藏可见的元素,并检查是否已让动画 GIF 旋转起来。如果您有,则需要弹出一些 CSS 或 JavaScript 来将 display: none
或 visibility: hidden
应用于它或其父元素。当然,如果它只是一张背景图片,那么您应该务必将其移除。
如果您想在实际网站中查看此行为的示例,请访问 Allegro,其中每个商品图片都有一个模糊处理的加载器 GIF,而不是明确隐藏的加载器 GIF。
总结
实现 60fps 意味着仅执行呈现网页所需的操作,不执行任何其他操作。清除多余的油漆是实现这一目标的关键一步。保持运行状态的动画 GIF 可能会触发不必要的绘制操作,您可以使用 DevTools 的“显示绘制矩形”工具轻松查找和调试这些操作。
您不会让猫咪加载器 GIF 动画一直运行下去,对吧?