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 会通过以下几种方式影响页面性能:
- 在网页的初始渲染期间。将 CSS 应用于网页时,系统会创建一个与 DOM 类似的结构,称为 CSS 对象模型 (CSSOM)。随着 CSS 选择器的具体性增加,CSSOM 会变得更加复杂,并且需要更多时间来运行必要的布局、样式设置、合成和绘制工作,以便将网页绘制到屏幕上。这项额外的工作会增加网页加载期间早期发生的互动延迟时间。
- 当互动通过元素插入或删除,或通过修改 DOM 内容和样式来修改 DOM 时,渲染该更新所需的工作可能会导致布局、样式、合成和绘制工作成本非常高。与页面初始渲染的情况一样,当 HTML 元素作为交互结果插入到 DOM 中时,CSS 选择器特异性的增加可能会增加渲染工作。
- 当 JavaScript 查询 DOM 时,对 DOM 元素的引用会存储在内存中。例如,如果您调用
document.querySelectorAll
来选择网页上的所有<div>
元素,如果结果返回大量 DOM 元素,则内存开销可能会相当大。
![一张屏幕截图,显示了 Chrome DevTools 性能面板中因渲染工作过多而导致的长时间任务。长任务的调用堆栈显示,在重新计算页面样式以及预绘制上花费了大量时间。](https://web.dev/static/articles/dom-size-and-interactivity/image/a-screenshot-a-long-task-a768ec4a643a9.png?authuser=8&hl=zh-cn)
所有这些因素都可能会影响互动性,但上方列表中的第二项尤为重要。如果互动导致 DOM 发生更改,则可能会触发大量工作,这可能会导致网页上的 INP 较差。
如何衡量 DOM 大小?
您可以通过多种方式测量 DOM 大小。第一种方法使用 Lighthouse。运行审核时,当前网页 DOM 的统计信息将显示在“避免 DOM 规模过大”审核的“诊断”标题下。在此部分中,您可以查看 DOM 元素的总数、包含子元素最多的 DOM 元素,以及最深的 DOM 元素。
更简单的方法是使用任何主流浏览器的开发者工具中的 JavaScript 控制台。如需获取 DOM 中的 HTML 元素总数,您可以在网页加载后在控制台中使用以下代码:
document.querySelectorAll('*').length;
如果您想实时查看 DOM 大小更新,还可以使用性能监控工具。借助此工具,您可以将布局和样式操作(以及其他性能方面)与当前 DOM 大小相关联。
![Chrome DevTools 中性能监视器的屏幕截图。左侧列出了页面生命周期内可持续监控的各种页面性能方面。在屏幕截图中,系统正在积极监控 DOM 节点数、每秒布局次数和每个部分的样式重新计算次数。](https://web.dev/static/articles/dom-size-and-interactivity/image/a-screenshot-the-perform-d46dd7f32a5de.png?authuser=8&hl=zh-cn)
如果 DOM 的大小接近 Lighthouse DOM 大小的警告阈值,或者完全失败,则下一步是确定如何减小 DOM 的大小,以提高网页响应用户互动的能力,从而提升网站的 INP。
如何衡量互动影响的 DOM 元素数量?
如果您在实验室中分析某个互动速度缓慢的问题,并怀疑该问题可能与网页 DOM 的大小有关,则可以选择性能分析器中标记为“重新计算样式”的任何活动,然后观察底部面板中的上下文数据,以确定受影响的 DOM 元素数量。
![Chrome DevTools 性能面板中所选样式重新计算活动的屏幕截图。顶部的“互动”轨迹显示了一次点击互动,其中大部分工作都用于执行样式重新计算和预绘制工作。底部的面板会显示所选 activity 的更多详细信息,其中报告了 2,547 个 DOM 元素受到了影响。](https://web.dev/static/articles/dom-size-and-interactivity/image/a-screenshot-selected-st-9a6c0cc217aa3.png?authuser=8&hl=zh-cn)
在上述屏幕截图中,请注意,选择重新计算作品的样式后,系统会显示受影响的元素数量。虽然上面的屏幕截图显示了 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 结构对样式的影响,不妨使用更现代(且更快)的布局模式,例如 flexbox 或 grid。
其他值得一试的策略
即使您费心地扁平化 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 会降低,从而带来更好的用户体验。