浏览器绘制和 Web 性能的考量

Avatar of Georgy Marchuk
Georgy Marchuk 发布

DigitalOcean 为您的旅程每个阶段提供云产品。立即开始使用 200 美元的免费积分!

网页浏览器将 HTML、CSS 和 JavaScript 转变为最终的视觉表示的过程相当复杂,涉及不少魔法。以下是对浏览器执行步骤的简化描述:

  1. 浏览器创建 DOM 和 CSSOM。
  2. 浏览器创建渲染树,其中考虑了来自 CSSOM 的 DOM 和样式(display: none 元素被避免)。
  3. 浏览器根据渲染树计算布局的几何形状及其元素。
  4. 浏览器逐像素绘制以创建我们在屏幕上看到的视觉表示。

在本文中,我想重点介绍最后一部分:绘制

所有这些步骤加起来对浏览器来说在加载时需要做很多工作……实际上,不仅在加载时,而且在 DOM(或 CSSOM)发生更改时也是如此。这就是为什么许多 Web 开发人员倾向于使用某种前端框架(如 React)来部分解决这个问题的原因,React 除了其他许多优点之外,还可以帮助高度优化 DOM 中的更改,以避免不必要的重新计算或渲染。

您可能听说过诸如状态组件渲染不变性之类的术语。所有这些都与 DOM 更改的优化有关,或者换句话说,仅在必要时对 DOM 进行更改。

举个例子,Web 应用程序的状态可能会发生变化,这会导致 UI 发生变化。但是,某些(或许多)组件不受此更改的影响。React 有助于将对 DOM 的写入限制在实际受状态更改影响的元素,最终将渲染限制在 Web 应用程序尽可能小的部分。

DOM/CSSOM → render tree → layout → painting

但是,浏览器绘制以其独特的方式进行,因为它甚至可以在没有对 DOM 和/或 CSSOM 进行任何更改的情况下发生。

页面性能摘要示例

上图使用 Chrome 的 DevTools 中的性能面板生成(稍后将详细介绍),它显示了浏览器在页面重新加载后的记录时间(0-7.12s)内每个任务所花费的时间。如您所见,绘制占用了很大一部分,但这并不一定是坏事。在这个特定示例中,绘制增加是由页面上的动画 GIF 和画布绘制(以 60fps)的组合造成的,它们都没有对 DOM 或其样式进行任何更改,但仍然触发了绘制。

另一个可能导致无需任何外部干预即可进行绘制的特性是 CSS animation 属性,与动画 GIF 或画布相比,它可能在 Web 上更常见。动画通常由用户输入(如悬停)触发,但借助于 animation@keyframes 规则,我们甚至可以创建相当复杂的动画,这些动画在页面上不断运行,而无需花费太多精力,这真是太棒了。

有些人可能没有意识到的是,这些动画很容易失控并不断触发绘制,这会消耗我们大量的处理能力。当然,有一些规则可以用来避免绘制。最明显的是将元素的操作限制在 CSS transformopacity 属性,它们默认情况下不会触发绘制,除非存在一些特殊情况,例如动画 SVG 路径。

绘制闪烁

您可能知道 Chrome 有 DevTools。您可能不知道的是一个小快捷键(Mac 上的 Shift+Cmd+P 或 PC 上的 Control+Shift+P),它可以在DevTools 内部使用来调出一个小的搜索栏和 命令菜单

命令菜单

我开始深入研究它,除了许多其他有用且非常有趣的选择之外,渲染面板引起了我的注意。

渲染面板

乍一看,您可以看到一些有趣的选择,这些选择在调试 Web 上的动画时非常有用,例如 FPS 计。

FPS 计

图层边框绘制闪烁也是有趣的工具。图层边框用于显示浏览器渲染图层的边框,以便可以轻松识别任何变换或尺寸变化。绘制闪烁用于突出显示浏览器被迫重新绘制网页区域。

绘制闪烁

发现绘制闪烁后,我做的第一件事就是在我自己的项目中检查它。在大多数地方,都没有问题。例如,网站上由滚动触发的任何移动都由 CSS transform 属性提供支持,如我们所述,它不会导致绘制。绘制出现在预期的地方,例如文本颜色在悬停时的更改,但由于其区域和仅在元素悬停时出现,因此这不是什么大问题。总而言之,即使您昨天编写了代码,也总能找到可以改进的地方……

但有一件事让我大吃一惊。

无论您经验丰富还是谨慎,您都可能——而且很可能——会犯错。我们只是普通人,有人会说,修复自己的错误是开发工作中最重要的部分。但是,要修复错误,我们需要意识到它的存在……这正是渲染面板提供帮助的地方。

案例研究

让我们仔细看看实际问题。设计要求有一个噪点背景。有点像老式电视在没有信号时出现的画面。

众所周知,GIF 有许多问题,其中性能肯定是一个问题,因此我绝对不能把它用作整个页面的背景。如果您想了解更多关于为什么要避免 GIF 的信息,这里有一个不错的 资源,其中包含许多原因。

在这种情况下,使用 JavaScript 绝对是一个选择。显示或隐藏带有略微移动背景的元素是我想到的第一件事,使用画布也可以。但是,所有这一切似乎有点过于复杂,仅仅是为了有一个背景。我决定采用纯 CSS 方式。

我的解决方案是使用一个小的“噪点”PNG 图像作为 background-image,启用 background-repeat 并将其放置在单色背景上。我如何实现噪点效果?使用无限的 CSS 动画!通过在 200 毫秒内将 background-position 设置为不同的值。以下是结果:

查看 CodePen 上 Georgy Marchuk (@gmrchk) 编写的 MXoddr

你能猜出问题吗?对我来说,这似乎是一个非常优雅的解决方案,并且我对自己没有使用糟糕的 GIF 甚至一行 JavaScript 就实现了这一点而感到兴奋。只是简单的 CSS,如今在浏览器中得到了优化。

好吧,绘制闪烁显示了完全不同的情况。窗口大小的图层不断重新绘制,即使用户什么也没做。如果在渲染面板中启用它,您可以在上面的演示中看到绘制闪烁(请注意,绘制闪烁不会在嵌入式笔中显示)。

没有绘制闪烁(左)与有绘制闪烁(右)

这当然不利于网站的性能,而且会像没有明天一样耗尽笔记本电脑的电池。

使用 background-position(顶部)和 transform(底部)完成动画的 CPU 使用情况

所有这些 CPU 使用量本可以通过使用 transformopacity 替换对 background-position 的更改来避免。

查看 CodePen 上 Georgy Marchuk (@gmrchk) 编写的 XYOYGm

问题

我已经进行 Web 开发一段时间了,我非常清楚动画背景绝不是一个好主意。这感觉像是一个菜鸟错误。人们会犯错……但事情并非如此简单。网站变得很卡,难以浏览。我怎么会错过这一点呢?

当然,我(以及您可能也是)在开发设备方面有点被宠坏了,这一点起着很大的作用。我有一台很棒的强大计算机用于工作,并且可以访问高速互联网。除非我们编写了一些非常糟糕的代码,否则我们编写的任何代码在我们看来都运行得非常流畅。但这并不总是适用于我们的用户。

类似的问题也适用于许多其他事情——比如显示尺寸。稍微夸大一点,虽然我们在 27 英寸 4K 分辨率的显示器上进行开发,并且主要获得 1920×1080 的设计,但我们的访问者主要来自 1366×768 的笔记本电脑,他们在使用计算机时的工作流程完全不同。

结论

虽然这篇文章最初是关于绘画的,但它的主要内容实际上更多地是关于注意我们的代码对绘画过程或性能的整体影响。虽然绘画是一个很好的例子,它可能存在问题并且很容易被忽视,但更多的是开发人员和用户之间脱节的问题。

网络是一个充满各种环境的地方,开发人员的环境通常与用户的环境大不相同。虽然我们不需要改变我们的方式或切换到延迟的计算机,但定期以他人的视角查看我们的工作肯定会有所帮助。我的建议是:下班回家后,如果有一些空闲时间,尝试拿起你的旧电脑,在那里查看你的工作,这样可以更接近你的用户体验。

如果你身边没有这种类型的电脑,像渲染面板这样的工具会非常有用。