优化 Interaction to Next Paint

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

发布时间:2023 年 5 月 19 日,最后更新时间:2025 年 9 月 2 日

Interaction to Next Paint (INP) 是一项稳定的 Core Web Vitals 指标,通过观察用户访问网页期间发生的所有符合条件的互动的延迟时间,评估网页对用户互动的总体响应情况。最终 INP 值是观测到的最长互动时间(有时会忽略离群值)。

为了提供良好的用户体验,网站应努力将 Interaction to Next Paint 控制在 200 毫秒或更短的时间内。为确保大多数用户的体验能够达到此目标值,建议以网页加载量的第 75 百分位作为衡量阈值,并分别按移动端和桌面端进行细分。

INP 值在 200 毫秒或以下为良好,大于 500 毫秒为不佳,介于两者之间则需要改进。
INP 阈值

根据网站的不同,可能几乎没有互动,例如网页上大多是文字和图片,几乎没有互动元素。或者,对于文本编辑器或游戏等网站,可能会有数百甚至数千次互动。无论哪种情况,只要 INP 较高,用户体验就会受到影响。

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

找出导致 INP 较差的原因

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

在现场查找缓慢的互动

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

如果您不依赖 RUM 提供商来获取实测数据,INP 实测数据指南建议使用 PageSpeed Insights 查看 Chrome 用户体验报告 (CrUX) 数据,以帮助填补空白。CrUX 是 Core Web Vitals 计划的 Google 数据集,提供了数百万个网站(包括 INP)的指标概要。不过,CrUX 通常不会提供您从 RUM 提供商处获得的情境数据,以帮助您分析问题。因此,我们仍然建议网站尽可能使用 RUM 提供商,或实现自己的 RUM 解决方案,以补充 CrUX 中提供的数据。

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

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

优化互动

在确定慢速互动并在实验室中手动重现后,下一步就是对其进行优化。

互动可以细分为三个子部分:

  1. 输入延迟时间:从用户开始与网页互动时开始,到互动的事件回调开始运行。
  2. 处理时长,即事件回调运行至完成所需的时间。
  3. 呈现延迟时间,即浏览器呈现包含互动视觉结果的下一帧所需的时间。
主线程上的互动示例。用户在阻塞任务运行时进行输入。输入会延迟到这些任务完成,之后 pointerup、mouseup 和 click 事件处理脚本会运行,然后渲染和绘制工作会启动,直到下一帧呈现。
互动的生命周期。在事件处理脚本开始运行之前,会发生输入延迟,这可能是由主线程上的长任务等因素造成的。然后,系统会运行互动的事件处理脚本回调,并在下一帧呈现之前出现延迟。

这三个子部分的总和就是总互动延迟时间。互动的每个子部分都会增加总互动延迟时间,因此请务必了解如何优化互动的每个部分,使其运行时间尽可能短。

识别并减少输入延迟

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

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

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

在网页生命周期中,互动性的一个关键方面是在启动期间。网页在加载时会先进行渲染,但请务必记住,网页渲染完毕并不意味着网页已加载完毕。根据网页需要多少资源才能完全正常运行,用户可能会在网页仍在加载时尝试与其互动。

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

根据脚本的大小,此工作可能会在主线程上引入长任务,从而导致浏览器延迟响应其他用户互动。为了让网页在加载期间能够及时响应用户输入,请务必了解如何降低网页加载期间出现长任务的可能性,从而让网页保持流畅。

优化事件回调

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

经常让出主线程

关于优化事件回调,最实用的建议是尽可能减少在回调中执行的操作。不过,您的互动逻辑可能很复杂,您可能只能略微减少它们执行的工作。

如果您发现自己的网站存在这种情况,接下来可以尝试将事件回调中的工作拆分为单独的任务。这样可以防止这些工作合并成一个长任务,从而阻塞主线程,让原本需要在主线程上等待的其他互动能够更快运行。

setTimeout 是拆分任务的一种方法,因为传递给它的回调会在新任务中运行。您可以单独使用 setTimeout,也可以将其使用抽象到单独的函数中,以便更符合人体工程学地让出。

不加区分地让出总比完全不让出要好,不过,还有一种更细致的方法可以向主线程让出控制权,那就是仅在更新界面的事件回调之后立即让出控制权,以便渲染逻辑可以更快运行。

让出控制权,以便更快地执行渲染工作

更高级的让出技术涉及在事件回调中构建代码,以将运行的内容限制为仅应用下一帧的视觉更新所需的逻辑。其他所有内容都可以推迟到后续任务中。这不仅能让回调保持轻量和敏捷,还能防止视觉更新阻塞事件回调代码,从而缩短互动的呈现时间。

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

在此示例中,需要执行以下四项操作来响应用户输入的字符。不过,只有第一项需要在下一帧呈现之前完成。

  1. 使用用户输入的内容更新文本框,并应用任何所需的格式。
  2. 更新界面中显示当前单词数的部分。
  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);
  });
});

下图展示了将所有非关键更新推迟到下一帧之后,如何缩短处理时长,从而缩短总体互动延迟时间。

两种场景下的键盘互动和后续任务示意图。在上图中,渲染关键任务和所有后续后台任务会同步运行,直到有机会呈现帧。在底部图表中,对渲染至关重要的工作会先运行,然后让出主线程,以便更快地呈现新帧。后台任务随后运行。
点击图表可查看高分辨率版本。

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

避免布局抖动

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

Chrome 开发者工具的“性能”面板中显示的布局抖动可视化图表。
Chrome 开发者工具的“性能”面板中显示的布局抖动示例。涉及布局抖动的渲染任务会在调用堆栈部分的右上角标有红色三角形,通常标记为重新计算样式或布局。

布局抖动是一个性能瓶颈,因为通过更新样式,然后立即在 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,您不仅需要承担 JavaScript 处理创建该 HTML 的费用,而且浏览器在完成解析该 HTML 并呈现该 HTML 之前不会让出。

不过,请务必记住,即使不是 SPA 的网站,也可能会因互动而通过 JavaScript 进行一定程度的 HTML 渲染。这通常没有问题,只要您不在客户端上呈现大量 HTML 即可,因为这可能会延迟下一帧的呈现。不过,请务必了解这种在浏览器中呈现 HTML 的方法对性能的影响,以及如果您使用 JavaScript 呈现大量 HTML,这种方法对网站对用户输入的响应能力有何影响。

总结

提高网站的 INP 是一个迭代过程。当您在现场修复缓慢的互动时,很有可能(尤其是如果您的网站提供了大量互动性)您会开始发现其他缓慢的互动,并且您也需要对它们进行优化。

改善 INP 的关键在于坚持不懈。久而久之,您就可以将网页的响应速度提升到让用户对您提供的体验感到满意的水平。此外,当您为用户开发新功能时,您可能也需要经历相同的过程,来优化针对这些功能的互动。这需要花费时间和精力,但这些投入绝对物有所值。