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

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

这种解决办法无法解决:当您构建某个网页时,该网页将拥有一个文档对象模型 (DOM)。DOM 代表网页 HTML 的结构,可以通过 DOM 访问网页的结构和内容。

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

在具有非常大的 DOM 的页面中,如果修改或更新 DOM 的互动触发开销大的布局工作,从而影响页面快速响应的能力,就会出现问题。高开销的布局工作可能会影响页面的 Interaction to Next Paint (INP);如果您想让页面快速响应用户互动,请务必确保 DOM 大小仅根据需要调整。

页面的 DOM 何时过大?

根据 Lighthouse 的介绍,当页面超过 1,400 个节点时,其 DOM 大小会过大。当页面的 DOM 超过 800 个节点时,Lighthouse 将开始发出警告。以下面的 HTML 为例:

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

以上代码中有四个 DOM 元素:<ul> 元素及其三个 <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 开发者工具中性能监控器的屏幕截图。左侧列出了页面生命周期内可持续监控的各种页面性能方面。在该屏幕截图中,系统正在积极监控 DOM 节点数、每秒布局次数和每个部分的样式重新计算次数。
Chrome 开发者工具中的性能监视器。在此视图中,页面的 DOM 节点当前数量会以图表的形式显示,同时还会显示每秒执行的布局操作和样式重新计算次数。

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

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

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

Chrome 开发者工具“性能”面板中所选样式重新计算活动的屏幕截图。互动跟踪记录的顶部显示了点击互动,大部分工作都用在了样式重新计算和预绘制工作上。底部会显示一个面板,其中显示了所选 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 依然会非常庞大,并且随着其不断变化来响应用户互动,这些渲染工作也会开始。如果您属于这种情况,可以考虑采取一些其他策略来限制呈现工作。

考虑附加方法

在某些情况下,网页在首次呈现时,用户最初无法看到其中的大部分内容。这可能是一个通过在启动时省略 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 会降低,从而带来更好的用户体验。