避免不必要的绘制 - GIF 动画版

Paul Lewis

避免绘制对于实现丝滑流畅的帧速率至关重要,尤其是在移动设备上。不过,有时颜料也会出现在最意想不到的地方。本文将探讨 GIF 动画为何会导致不必要的绘制,以及您可以采取的非常简单的解决方法。

层层叠叠的可爱

您可能知道,现代浏览器可能会将多组 DOM 元素绘制到单独的“图片”(称为“层”)中。有时,整个网页只有一个层,有时有数百个,在极少数情况下,甚至有数千个!

当 DOM 元素被分组到一个图层中,并且其中一个元素发生视觉变化时,我们最终不仅要绘制发生变化的元素,还要绘制图层中与发生变化的元素重叠的所有其他元素。在原有内容上绘制新内容会导致被覆盖的像素实际上永远“丢失”;如果您想恢复原始像素,则需要重新绘制。

因此,有时我们希望将一个元素与其他元素隔离开来,这样在绘制该元素时,我们就不需要重新绘制其他未发生变化的元素。例如,如果您将固定页眉与可滚动内容结合使用,则每次滚动内容时,您都必须重新绘制页眉以及新显示的内容。通过将标题放置在单独的层中,浏览器可以优化滚动。当您滚动时,浏览器可以移动这些层(可能是在 GPU 的帮助下),并避免重新绘制任何层。

每个额外的图层都会增加内存消耗并增加性能开销,因此目标是在保持良好性能的同时,将页面分组到尽可能少的图层中。

这一切与 GIF 动画有什么关系?

我们来看看这张图片:

一个 Web 应用,分为四层。
图 1:Web 应用分为四个层。

这是一个简单应用的潜在图层设置。这里有四个图层:其中三个(图层 2 到 4)是界面元素;背景图层是一个加载器,恰好是一个 GIF 动画。在正常流程中,您会在应用加载时显示加载器(第 1 层),然后在所有内容加载完毕后显示其他层。但关键在于:您需要隐藏 GIF 动画。

但我为什么要隐藏它?!

问得好。在理想情况下,浏览器会为您检查 GIF 的可见性,并避免自动绘制。遗憾的是,检查 GIF 动画是否被遮挡或在屏幕上可见通常比直接绘制它更耗费资源,因此它会被绘制。

在最理想的情况下,GIF 位于其自己的层中,浏览器只需绘制该层并将其上传到 GPU。但在最坏的情况下,所有元素可能会被分组到单个层中,浏览器必须重新绘制每个元素。完成后,它仍需要将所有内容上传到 GPU。尽管用户甚至看不到 GIF,但每个 GIF 帧都会执行所有这些工作!

在桌面设备上,您或许可以采用这种绘制行为,因为桌面设备的 CPU 和 GPU 更强大,并且两者之间有足够的带宽来传输数据。不过,在移动设备上,绘制操作的开销非常高,因此您必须格外小心。

哪些浏览器会受到影响?

通常,不同浏览器的行为会有所不同。目前,即使 GIF 被遮挡,Chrome、Safari 和 Opera 也会重新绘制。而 Firefox 则会发现 GIF 被遮挡,不需要重新绘制。Internet Explorer 仍然是一个黑盒,即使在 IE11 中(因为 F12 工具仍在开发中),也没有任何迹象表明是否正在进行任何重绘。

如何判断我是否遇到了此问题?

最简单的方法是在 Chrome 开发者工具中使用“显示绘制矩形”。加载开发者工具,然后按右下角的齿轮 (齿轮图标),并在渲染部分下选择显示绘制矩形。

在 Chrome 开发者工具中启用“显示绘制矩形”
图 2:在 Chrome 开发者工具中启用“显示绘制矩形”。

现在,您只需寻找一个像这样的红色矩形:

DevTools 的“显示绘制矩形”功能会用红色矩形提示 GIF 动画问题。
图 3:DevTools 的“显示绘制矩形”功能通过红色矩形提示 GIF 动画存在问题。

屏幕上的红色小框表示 Chrome 正在重绘某些内容。您知道有一个 GIF 加载器隐藏在其他元素后面,因此当您看到这样的红框时,需要隐藏可见元素,并检查是否留下了 GIF 动画。如果是,您需要添加一些 CSS 或 JavaScript,以便将 display: none 或 visibility: hidden 应用于该元素或其父元素。当然,如果只是背景图片,则应确保将其移除。

如果您想在实际网站中查看此行为的示例,请访问 Allegro,其中每个商品的图片都有一个加载器 GIF,该 GIF 是被遮挡而不是被明确隐藏。

总结

实现 60fps 意味着仅执行渲染页面所需的操作,不多也不少。去除多余的油漆是实现这一目标的关键步骤。GIF 动画如果一直运行,可能会触发不必要的绘制,您可以使用开发者工具的“显示绘制矩形”工具轻松找到并调试这些问题。

现在,您不会让那个小猫加载动画 GIF 一直运行下去,对吧?