优化 Interaction to Next Paint

了解如何优化您网站的 Interaction to Next Paint。

Interaction to Next Paint (INP) 是一个稳定版 Core Web Vitals 指标,它通过观察用户访问页面期间发生的所有符合条件的互动的延迟时间,来评估页面对用户互动的总体响应速度。最终 INP 值是观察到的最长互动(有时会忽略异常值)。

为了提供良好的用户体验,网站应力争将 Interaction to Next Paint 控制在 200 毫秒或更短。为了确保您为大多数用户达到此目标,一个好的衡量阈值是页面加载的第 75 百分位数,按移动设备和桌面设备细分。

Good INP values are 200 milliseconds or less, poor values are greater than 500 milliseconds, and anything in between needs improvement.

根据网站的不同,可能几乎没有互动,例如主要是文本和图像且几乎没有互动元素的页面。或者,对于文本编辑器或游戏等网站,可能会有数百甚至数千次互动。在任何一种情况下,如果 INP 值很高,用户体验都会面临风险。

改进 INP 需要时间和精力,但回报是更好的用户体验。在本指南中,将探讨改进 INP 的途径。

找出导致 INP 较差的原因

在修复缓慢的互动之前,您需要数据来告诉您网站的 INP 是否较差或需要改进。获得这些信息后,您可以进入实验室开始诊断缓慢的互动,并逐步找到解决方案。

在现场数据中查找缓慢的互动

理想情况下,您优化 INP 的旅程将从现场数据开始。在最佳情况下,来自真实用户监控 (RUM) 提供商的现场数据不仅会为您提供页面的 INP 值,还会提供上下文数据,突出显示导致 INP 值的具体互动、互动是在页面加载期间还是加载后发生、互动类型(点击、按键或触摸)以及其他有价值的信息。

如果您不依赖 RUM 提供商来获取现场数据,INP 现场数据指南建议使用 PageSpeed Insights 中的 Chrome 用户体验报告 (CrUX) 来帮助填补空白。CrUX 是 Core Web Vitals 计划的官方数据集,并提供数百万个网站的指标(包括 INP)的概要总结。但是,CrUX 通常不提供您从 RUM 提供商处获得的上下文数据,以帮助您分析问题。因此,我们仍然建议网站在可能的情况下使用 RUM 提供商,或者实施自己的 RUM 解决方案,以补充 CrUX 中可用的内容。

在实验室中诊断缓慢的互动

理想情况下,一旦您有现场数据表明您存在缓慢的互动,您就需要开始在实验室中进行测试。在没有现场数据的情况下,有一些策略可以在实验室中识别缓慢的互动。这些策略包括遵循常见的用户流程并在沿途测试互动,以及在页面加载期间(此时主线程通常最繁忙)与页面互动,以便在该用户体验的关键部分浮现缓慢的互动。

优化互动

一旦您识别出缓慢的互动并可以在实验室中手动重现它,下一步就是优化它。互动可以分为三个阶段

  1. 输入延迟,它在用户发起与页面的互动时开始,并在互动的事件回调开始运行时结束。
  2. 处理时长,它包括事件回调运行完成所需的时间。
  3. 呈现延迟,它是浏览器呈现包含互动视觉结果的下一帧所需的时间。

这三个阶段的总和是总互动延迟。互动的每个阶段都会为总互动延迟贡献一定的时间量,因此了解如何优化互动的每个部分以使其尽可能短的时间运行非常重要。

识别和减少输入延迟

当用户与页面互动时,该互动的第一个部分是输入延迟。根据页面上的其他活动,输入延迟的长度可能会相当长。这可能是由于主线程上发生的活动(可能是由于脚本加载、解析和编译)、fetch 处理、计时器功能,甚至来自快速连续发生并相互重叠的其他互动。

无论互动的输入延迟的来源是什么,您都希望将输入延迟降至最低,以便互动可以尽快开始运行事件回调。

启动期间脚本评估与长任务之间的关系

页面生命周期中交互性的一个关键方面是在启动期间。当页面加载时,它将首先呈现,但重要的是要记住,仅仅因为页面已呈现,并不意味着页面已完成加载。根据页面完全正常运行所需的资源数量,用户可能在页面仍在加载时尝试与页面互动。

在页面加载时可能会延长互动输入延迟的一件事是脚本评估。在从网络获取 JavaScript 文件后,浏览器仍然需要完成一些工作才能运行该 JavaScript;该工作包括解析脚本以确保其语法有效、将其编译为字节码,然后最终执行它。

根据脚本的大小,这项工作可能会在主线程上引入长任务,这将延迟浏览器响应其他用户互动。为了在页面加载期间保持页面对用户输入的响应,重要的是要了解您可以做些什么来减少页面加载期间长任务的可能性,以便页面保持快速响应。

优化事件回调

输入延迟只是 INP 衡量标准的第一部分。您还需要确保响应用户互动而运行的事件回调可以尽快完成。

经常让位于主线程

优化事件回调的最佳通用建议是在其中尽可能少地完成工作。但是,您的互动逻辑可能很复杂,您可能只能略微减少它们所做的工作。

如果您发现您的网站是这种情况,您可以尝试的下一件事是将事件回调中的工作分解为单独的任务。这可以防止集体工作变成阻塞主线程的长任务,从而允许原本会在主线程上等待的其他互动更快地运行。

setTimeout 是分解任务的一种方法,因为传递给它的回调在新任务中运行。您可以单独使用 setTimeout,或将其用法抽象为单独的函数以获得更符合人体工程学的让步

不加区分地让步总比不让步要好,但是,有一种更细致的方式可以向主线程让步,即仅在更新用户界面的事件回调之后立即让步,以便渲染逻辑可以更快地运行。

让步以允许渲染工作更快发生

一种更高级的让步技术涉及在事件回调中构建代码,以限制仅运行为下一帧应用视觉更新所需的逻辑。所有其他内容都可以推迟到后续任务。这不仅使回调保持轻巧灵活,而且还通过不允许视觉更新阻止事件回调代码来改善互动的渲染时间。

例如,假设一个富文本编辑器,它会在您键入时格式化文本,还会更新 UI 的其他方面以响应您所写的内容(例如字数统计、突出显示拼写错误和其他重要的视觉反馈)。此外,应用程序可能还需要保存您编写的内容,以便如果您离开并返回,您不会丢失任何工作。

在此示例中,以下四件事需要在响应用户键入的字符时发生。但是,只有第一项需要在呈现下一帧之前完成。

  1. 使用用户键入的内容更新文本框,并应用任何所需的格式。
  2. 更新 UI 中显示当前字数统计的部分。
  3. 运行逻辑以检查拼写错误。
  4. 保存最近的更改(本地或远程数据库)。

执行此操作的代码可能如下所示

textBox.addEventListener('input', (inputEvent) => {
  // Update the UI immediately, so the changes the user made
  // are visible as soon as the next frame is presented.
  updateTextBox(inputEvent);

  // Use `setTimeout` to defer all other work until at least the next
  // frame by queuing a task in a `requestAnimationFrame()` callback.
  requestAnimationFrame(() => {
    setTimeout(() => {
      const text = textBox.textContent;
      updateWordCount(text);
      checkSpelling(text);
      saveChanges(text);
    }, 0);
  });
});

以下可视化效果显示了将任何非关键更新推迟到下一帧之后如何减少处理时长,从而减少总体互动延迟。

A depiction of a keyboard interaction and subsequent tasks in two scenarios. In the top figure, the render-critical task and all subsequent background tasks run synchronously until the opportunity to present a frame has arrived. In the bottom figure, the render-critical work runs first, then yields to the main thread to present a new frame sooner. The background tasks run thereafter.
单击上图可查看高分辨率版本。

虽然在 requestAnimationFrame() 调用中使用 setTimeout() 的前一个代码示例确实有点深奥,但它是一种在所有浏览器中都有效的有效方法,可确保非关键代码不会阻止下一帧。

避免布局抖动

布局抖动(有时称为强制同步布局)是一种渲染性能问题,其中布局同步发生。当您在 JavaScript 中更新样式,然后在同一任务中读取它们时,就会发生这种情况——并且JavaScript 中有很多属性可能会导致布局抖动

A visualization of layout thrashing as shown in the performance panel of Chrome DevTools.
布局抖动的示例,如 Chrome DevTools 的性能面板中所示。涉及布局抖动的渲染任务将在调用堆栈部分的右上角用红色三角形标记,通常标记为 Recalculate StyleLayout

布局抖动是一个性能瓶颈,因为通过更新样式然后立即在 JavaScript 中请求这些样式的值,浏览器被迫执行同步布局工作,否则它可以等到事件回调运行完成后再异步执行。

最大限度地减少呈现延迟

互动的呈现延迟标记了从互动的事件回调完成运行到浏览器能够绘制下一帧以显示结果视觉变化的时间跨度。

最大限度地减小 DOM 大小

当页面的 DOM 很小时,渲染工作通常会很快完成。但是,当 DOM 变得非常大时,渲染工作往往会随着 DOM 大小的增加而扩展。渲染工作与 DOM 大小之间的关系不是线性的,但大型 DOM 确实比小型 DOM 需要更多的工作才能渲染。大型 DOM 在两种情况下存在问题

  1. 在初始页面渲染期间,大型 DOM 需要大量工作来渲染页面的初始状态。
  2. 响应用户互动时,大型 DOM 可能会导致渲染更新非常昂贵,因此会增加浏览器呈现下一帧所需的时间。

请记住,在某些情况下,大型 DOM 无法显着缩小。虽然您可以采取一些方法来减小 DOM 大小,例如展平您的 DOM在用户互动期间添加到 DOM 以保持您的初始 DOM 大小较小,但这些技术可能只能做到这种程度。

使用 content-visibility 延迟渲染屏幕外元素

您可以限制页面加载期间的渲染工作和响应用户互动时的渲染工作量的一种方法是依靠 CSS content-visibility 属性,该属性实际上相当于在元素接近视口时延迟渲染元素。虽然 content-visibility 可能需要一些练习才能有效使用,但如果结果是更低的渲染时间,可以改善您页面的 INP,则值得研究。

注意使用 JavaScript 渲染 HTML 时的性能成本

有 HTML 的地方就有 HTML 解析,在浏览器完成将 HTML 解析为 DOM 后,它必须将样式应用于它,执行布局计算,然后随后渲染该布局。这是不可避免的成本,但如何进行渲染 HTML 很重要。

当服务器发送 HTML 时,它会作为流到达浏览器。流式传输意味着来自服务器的 HTML 响应以块的形式到达。浏览器通过增量解析到达的流块并逐位渲染它们来优化其处理流的方式。这是一种性能优化,因为浏览器在页面加载期间隐式地定期和自动地让步,您可以免费获得它。

虽然首次访问任何网站总是会涉及一些 HTML 量,但一种常见的方法是从最少的初始 HTML 位开始,然后使用 JavaScript 来填充内容区域。对该内容区域的后续更新也会作为用户互动的结果而发生。这通常称为 单页应用程序 (SPA) 模型。这种模式的一个缺点是,通过在客户端使用 JavaScript 渲染 HTML,您不仅会获得创建该 HTML 的 JavaScript 处理成本,而且浏览器在完成解析该 HTML 和渲染它之前不会让步。

但至关重要的是要记住,即使是 SPA 的网站也可能会涉及通过 JavaScript 渲染一些 HTML 作为互动的结果。这通常没问题,只要您不在客户端渲染大量 HTML,这可能会延迟下一帧的呈现。但是,重要的是要了解这种在浏览器中渲染 HTML 的方法的性能影响,以及如果您通过 JavaScript 渲染大量 HTML,它如何影响您网站对用户输入的响应能力。

结论

改进您网站的 INP 是一个迭代过程。当您修复现场数据中缓慢的互动时,很有可能(尤其是当您的网站提供大量互动时)您会开始找到其他缓慢的互动,并且您也需要优化它们。

改进 INP 的关键在于坚持不懈。随着时间的推移,您可以将页面的响应速度提升到用户对您提供的体验感到满意的位置。当您为用户开发新功能时,您很可能还需要经历相同的过程来优化特定于这些功能的互动。这将需要时间和精力,但这是值得花费的时间和精力。

英雄图片来自 Unsplash,由 David Pisnoy 拍摄并根据 Unsplash 许可修改。