DOM 大小对互动的影响以及应对措施

DOM 大小过大对交互性的影响比您想象的要大。本指南介绍了原因以及您可以采取的措施。

这是无法避免的:当您构建网页时,该网页将具有文档对象模型 (DOM)。DOM 表示网页 HTML 的结构,并允许 JavaScript 和 CSS 访问网页的结构和内容。

不过,问题在于 DOM 的大小会影响浏览器快速高效地渲染网页的能力。一般而言,DOM 越大,初始渲染该网页和在网页生命周期后期更新其渲染的开销就越高。

在 DOM 非常大的页面中,如果修改或更新 DOM 的互动触发了昂贵的布局工作,从而影响了页面快速响应的能力,就会出现问题。复杂的布局工作可能会影响网页的 Interaction to Next Paint (INP);如果您希望网页能够快速响应用户互动,请务必确保 DOM 大小仅在必要时才会变大。

什么时候网页的 DOM 会过大

根据 Lighthouse,如果网页的 DOM 大小超过 1,400 个节点,则 DOM 大小过大。当网页的 DOM 超过 800 个节点时,Lighthouse 将开始抛出警告。以以下 HTML 为例:

<ul>
  <li>List item one.</li>
  <li>List item two.</li>
  <li>List item three.</li>
</ul>

在上面的代码中,有 4 个 DOM 元素:<ul> 元素及其 3 个 <li> 子元素。您的网页肯定会有比这个数量多得多的节点,因此请务必了解如何控制 DOM 大小,以及在将网页的 DOM 缩小到最小后,如何采用其他策略来优化渲染工作。

较大的 DOM 对网页性能有何影响?

较大的 DOM 会通过以下几种方式影响页面性能:

  1. 在网页的初始渲染期间。将 CSS 应用于网页时,系统会创建一个与 DOM 类似的结构,称为 CSS 对象模型 (CSSOM)。随着 CSS 选择器的具体性增加,CSSOM 会变得更加复杂,并且需要更多时间来运行必要的布局、样式设置、合成和绘制工作,以便将网页绘制到屏幕上。这项额外的工作会增加网页加载期间早期发生的互动延迟时间。
  2. 当互动通过元素插入或删除,或通过修改 DOM 内容和样式来修改 DOM 时,渲染该更新所需的工作可能会导致布局、样式、合成和绘制工作成本非常高。与页面初始渲染的情况一样,当 HTML 元素作为交互结果插入到 DOM 中时,CSS 选择器特异性的增加可能会增加渲染工作。
  3. 当 JavaScript 查询 DOM 时,对 DOM 元素的引用会存储在内存中。例如,如果您调用 document.querySelectorAll 来选择网页上的所有 <div> 元素,如果结果返回大量 DOM 元素,则内存开销可能会相当大。
一张屏幕截图,显示了 Chrome DevTools 性能面板中因渲染工作过多而导致的长时间任务。长任务的调用堆栈显示,在重新计算页面样式以及预绘制上花费了大量时间。
Chrome DevTools 性能分析器中显示的长时间任务。显示的长任务是由通过 JavaScript 将 DOM 元素插入大型 DOM 所致。

所有这些因素都可能会影响互动性,但上方列表中的第二项尤为重要。如果互动导致 DOM 发生更改,则可能会触发大量工作,这可能会导致网页上的 INP 较差。

如何衡量 DOM 大小?

您可以通过多种方式测量 DOM 大小。第一种方法使用 Lighthouse。运行审核时,当前网页 DOM 的统计信息将显示在“避免 DOM 规模过大”审核的“诊断”标题下。在此部分中,您可以查看 DOM 元素的总数、包含子元素最多的 DOM 元素,以及最深的 DOM 元素。

更简单的方法是使用任何主流浏览器的开发者工具中的 JavaScript 控制台。如需获取 DOM 中的 HTML 元素总数,您可以在网页加载后在控制台中使用以下代码:

document.querySelectorAll('*').length;

如果您想实时查看 DOM 大小更新,还可以使用性能监控工具。借助此工具,您可以将布局和样式操作(以及其他性能方面)与当前 DOM 大小相关联。

Chrome DevTools 中性能监视器的屏幕截图。左侧列出了页面生命周期内可持续监控的各种页面性能方面。在屏幕截图中,系统正在积极监控 DOM 节点数、每秒布局次数和每个部分的样式重新计算次数。
Chrome 开发者工具中的性能监视器。在此视图中,页面的 DOM 节点当前数量会以图表的形式显示,同时还会显示每秒执行的布局操作和样式重新计算次数。

如果 DOM 的大小接近 Lighthouse DOM 大小的警告阈值,或者完全失败,则下一步是确定如何减小 DOM 的大小,以提高网页响应用户互动的能力,从而提升网站的 INP。

如何衡量互动影响的 DOM 元素数量?

如果您在实验室中分析某个互动速度缓慢的问题,并怀疑该问题可能与网页 DOM 的大小有关,则可以选择性能分析器中标记为“重新计算样式”的任何活动,然后观察底部面板中的上下文数据,以确定受影响的 DOM 元素数量。

Chrome DevTools 性能面板中所选样式重新计算活动的屏幕截图。顶部的“互动”轨迹显示了一次点击互动,其中大部分工作都用于执行样式重新计算和预绘制工作。底部的面板会显示所选 activity 的更多详细信息,其中报告了 2,547 个 DOM 元素受到了影响。
观察样式重新计算工作导致 DOM 中受影响的元素数量。请注意,互动轨迹中互动时间的阴影部分表示互动时长超过 200 毫秒的部分,这是为 INP 指定的“良好”阈值

在上述屏幕截图中,请注意,选择重新计算作品的样式后,系统会显示受影响的元素数量。虽然上面的屏幕截图显示了 DOM 大小对包含许多 DOM 元素的页面上的渲染工作的影响的极端情况,但无论如何,此诊断信息都非常有用,可用于确定 DOM 大小是否是限制下一个帧在响应互动时绘制所需时间的因素。

如何减小 DOM 大小?

除了审核网站的 HTML 以查找不必要的标记之外,缩减 DOM 大小的主要方法是缩减 DOM 深度。如果您在浏览器开发者工具的 Elements(元素)标签页中看到如下所示的标记,则表明您的 DOM 可能不必要地过深:

<div>
  <div>
    <div>
      <div>
        <!-- Contents -->
      </div>
    </div>
  </div>
</div>

当您看到这样的模式时,可以通过扁平化 DOM 结构来简化它们。这样可以减少 DOM 元素的数量,并且可能为您提供简化页面样式的机会。

DOM 深度也可能是您所用框架的症状。特别是,基于组件的框架(例如依赖于 JSX 的框架)要求您将多个组件嵌套在父容器中。

不过,许多框架都允许您使用所谓的 fragment 来避免嵌套组件。以组件为基础且提供 fragment 作为功能的框架包括但不限于:

通过在所选框架中使用 fragment,您可以减少 DOM 深度。如果您担心扁平化 DOM 结构对样式的影响,不妨使用更现代(且更快)的布局模式,例如 flexboxgrid

其他值得一试的策略

即使您费心地扁平化 DOM 树并移除不必要的 HTML 元素,以尽可能缩小 DOM 的大小,但它可能仍然很大,并且会在响应用户互动时发生变化,从而启动大量渲染工作。如果您遇到这种情况,可以考虑采用一些其他策略来限制渲染工作。

考虑采用叠加方法

在某些情况下,网页首次呈现时,用户最初无法看到网页的大部分内容。这可能是一个通过在启动时省略 DOM 的这些部分来延迟加载 HTML 的机会,但在用户与需要网页最初隐藏部分的网页部分互动时添加这些部分。

这种方法在初始加载期间和之后都很有用。对于初始网页加载,您需要预先完成的渲染工作更少,这意味着初始 HTML 载荷会更轻,并且会更快地渲染。这样一来,在关键时刻,系统就会为互动提供更多运行机会,而主线程的注意力竞争也会减少。

如果网页的许多部分在加载时最初处于隐藏状态,这还可以加快触发重新渲染工作的其他互动。不过,随着其他互动向 DOM 添加更多内容,随着 DOM 在整个网页生命周期内不断增长,渲染工作量也会增加。

随着时间的推移,向 DOM 中添加内容可能会很棘手,并且会带来一些权衡。如果您采用这种方式,则可能需要发出网络请求来获取数据,以填充您打算在响应用户互动时添加到网页中的 HTML。虽然正在处理的网络请求不会计入 INP,但可能会增加感知延迟时间。如果可能,请显示正在加载的旋转图标或其他指示器,以便用户了解正在发生什么。

限制 CSS 选择器的复杂性

当浏览器解析 CSS 中的选择器时,它必须遍历 DOM 树,以了解这些选择器是否以及如何应用于当前布局。这些选择器越复杂,浏览器就必须执行更多工作才能执行网页的初始渲染,并且如果网页因互动而发生变化,还需要执行更多样式重新计算和布局工作。

使用 content-visibility 属性

CSS 提供了 content-visibility 属性,这是一种有效的延迟渲染屏幕外 DOM 元素的方法。当元素接近视口时,系统会按需渲染这些元素。content-visibility 的好处不仅在于可以减少初始网页呈现的大量呈现工作,还可以在用户互动导致网页 DOM 发生更改时跳过屏幕外元素的呈现工作。

总结

将 DOM 大小缩减到仅包含严格必要的内容,是优化网站 INP 的好方法。这样一来,您就可以缩短浏览器在 DOM 更新时执行布局和渲染工作所需的时间。即使您无法显著缩减 DOM 大小,也可以使用一些技术将渲染工作隔离到 DOM 子树,例如 CSS 封装和 content-visibility CSS 属性。

无论您采用何种方法,只要创建一个尽可能减少渲染工作量的环境,并减少网页在响应互动时执行的渲染工作量,最终都会让用户在与您的网站互动时感觉到更快的响应速度。这意味着,您网站的 INP 会降低,从而带来更好的用户体验。