Interaction to Next Paint - 处理时间
了解如何查找和改善由处理时间引起的 INP 问题

由处理时间引起的 Interaction to Next Paint (INP) 问题
在我们之前的文章中,我们讨论了 Interaction to Next Paint 以及如何识别 Interaction to Next Paint 问题。如果您想了解基础知识,这是一个很好的起点!
在本文中,我将重点讨论"处理时间"。它如何影响 Interaction to Next Paint 问题,然后解释如何最小化处理时间以改善 Interaction to Next Paint!
简而言之:Interaction to Next Paint (INP) 衡量的是用户与页面交互后,页面发生视觉变化所需的时间。INP 可以分解为 3 个组成部分:"输入延迟"、"处理时间"和"呈现延迟"。处理时间是总 INP 的主要贡献者,平均约占延迟的 40%。这意味着优化您的 JavaScript 代码,特别是事件处理程序,可以显著影响您网站的 INP 分数。
INP 提示:可以通过立即运行布局更新之前的重要代码,并将所有其他代码安排在之后运行来优化处理时间。
Table of Contents!
什么是处理时间?

Interaction to Next Paint (INP) 可以分解为 3 个子部分:"输入延迟"、"处理时间"和"呈现延迟"
处理时间是指用户与网页交互(例如,点击按钮或按下键)后,浏览器处理相关事件回调所需的时间。虽然总是存在一些处理时间,但当事件回调占用过多处理时间时,就会出现 INP 问题。
处理时间与 INP
当您考虑优化 Interaction to Next Paint 时,处理时间可能是"关键因素"。它是在浏览器更新布局之前"需要完成的工作"。
许多开发者认为改善 INP 就是优化回调函数(优化处理时间),他们是对的。但就重要性而言,处理时间甚至不是最需要改善的部分,但它平均仍然占总 INP 时间的约 40%。

在 CoreDash,我们每小时收集数百万个 Core Web Vitals 数据点。根据这些数据,处理时间占 Interaction to Next Paint 的 40%。虽然这个比例很大,但仅优化处理时间很可能不足以修复 INP 问题
处理时间示例:当用户与网页交互时,例如点击按钮,与该按钮点击相关的事件处理程序完成执行所需的时间称为处理时间。例如,如果用户点击按钮提交表单,验证表单数据、将其发送到服务器并处理响应的代码都会贡献处理时间。这些操作所需的时间越长,处理时间就越长,INP 分数可能就越差。
什么导致高处理时间?
要开始修复由高处理时间引起的 INP 问题,我们需要了解高处理时间的可能原因。高处理时间,即事件回调运行完成所需的时间,可能由不需要的代码、未优化的代码、集中的回调和布局抖动引起。让我们更仔细地看看这 4 个方面。

- 不需要的代码。旧的、未使用的代码或与用户交互没有直接关联的代码,可能会延长回调执行时间。
- 未优化的代码。低效的代码(通常是循环或低效的 DOM 查找)可能使事件回调运行得比需要的更慢。
- 集中的回调: 多个事件回调安排在很近的时间内会创建一个队列。如果用户交互触发的回调被卡在这个队列中,响应就会出现延迟。
- 布局抖动: 频繁的 DOM 操作触发布局重新计算,可能给浏览器带来压力并导致性能回退。
最小化处理时间

要最小化处理时间,我们需要使负责后续布局更新的代码尽可能快地运行。我们可以通过优化现有代码(删除不需要的代码和优化当前代码)以及区分需要在布局更新之前和之后运行的代码来实现这一点。基本上,对布局更新至关重要的代码需要在更新之前运行,所有其他代码可以在布局更新之后运行。
- 删除未使用的代码。 虽然删除未使用的代码看起来很简单,但在大多数网站上,至少有一些旧的未使用代码在执行,但实际上并没有为页面或 UX 添加任何内容。这意味着首先要做的是确保没有不需要的代码在运行。这可以通过多种方式完成。例如,通过一个叫做 tree shaking 或代码分割的过程。或者手动检查 Chrome 中的代码覆盖率,也可以使用一个好的 IDE 来提示未使用的代码。(专业提示:也要仔细审视您的 Tag Manager 加载的资源)
- 最小化回调执行时间:使用 JavaScript 分析器来识别代码中的瓶颈,并针对这些区域进行优化。考虑使用记忆化、预计算和缓存等技术来避免冗余计算。(提示:您可以使用 Chrome 性能面板来查找执行时间较长的脚本!)
- 优先处理关键代码并安排其他代码:当回调代码已被优化后,将代码分为需要立即运行的代码和可以延迟的代码。看看这个实际示例:

在此示例中,Google Tag Manager 和 Facebook 事件回调在布局更新之前的(REACT)代码之前执行。解决方案是在浏览器空闲时安排 GTM 回调 - 避免布局抖动或回流。布局抖动发生在样式更新和样式读取在循环中混合时,导致浏览器多次重新计算布局。
为避免布局抖动,在请求样式值("获取")之前执行所有样式更改("设置")。这种方法最大限度地减少了布局更新的频率,从而使网页更快。
例如,在一个将每个段落的宽度设置为匹配某元素宽度的循环中,在循环之前一次性读取该元素的宽度,然后在循环内使用该值来更新段落的宽度。
如何优先处理关键代码
最后一项"优先处理关键代码并安排其他代码"对许多人来说可能有些抽象。我们可以通过使用 requestIdleCallback() 和 yielding 到主线程来优先处理关键代码。
我们将 requestIdleCallback 用于不需要立即运行的不太重要的任务:以下是安排 GTM 事件的前后示例。
/* before :: immediately run code */
gtag('event', '<event_name>', {
'event_category': '<event_category>',
});
/* after :: run the same code during browser idle */
requestIdleCallback(() => {
gtag('event', '<event_name>', {
'event_category': '<event_category>',
});
}, { timeout: 1000 });
requestIdleCallback 的缺点是代码可能不会像您希望的那样快速运行。在这种情况下,我们可以在最重要的代码运行后"yield 到主线程",从而给浏览器一个运行布局更新的机会。以下是通过 yielding 到主线程来分解任务的示例
async function yieldToMain() {
if ('scheduler' in window && 'yield' in window.scheduler) {
return await window.scheduler.yield();
}
return new Promise((resolve) => {
setTimeout(resolve, 0);
});
}
async function handleClick(){
// do the most important layout updates here
await yieldToMain();
// do other tasks that need to run as asap after the layout update
}将来,我们可以使用 window.scheduler 做更多事情,而不仅仅是 yield 到主线程。我们还可以使用 scheduler API 来优先处理任务(参见 支持表)。
scheduler API 提供了 postTask() 函数,用于通过设置优先级来更精细地安排任务,这有助于浏览器优先处理工作,使低优先级任务 yield 到主线程。
postTask() 函数接受三个优先级设置:"background"用于最低优先级任务,"user-visible"用于中等优先级任务,"user-blocking"用于需要高优先级的关键任务。
通过使用"user-blocking"优先处理关键任务,开发者可以确保它们以更高的优先级执行,允许浏览器更响应地处理用户交互。scheduler API 提供了 postTask() 函数,用于通过设置优先级来更精细地安排任务,这有助于浏览器优先处理工作,使低优先级任务 yield 到主线程。
实际应用
让我们来看最重要的问题:"这对我的网站意味着什么"。让我们针对 WordPress 和 REACT 进行分析,看看在处理时间方面如何改善 Interaction to Next Paint。
WordPress
WordPress 在脚本方面提供的控制非常有限。许多脚本是通过插件添加的。大多数时候,这些脚本会向页面添加"事件监听器",除了延迟 Interaction to Next Paint (INP) 之外什么都不做。如果您的 WordPress 网站由于长处理时间而出现 Interaction to Next Paint 问题,请采取以下步骤:
- 检查主题设置。取消选中任何不需要的选项,如"平滑滚动"或"动画菜单"。这类设置往往会导致 INP 问题。
- 检查哪些脚本导致了长处理时间(提示:使用 Chrome 性能面板)。如果这些脚本与插件相关,考虑找另一个功能大致相同的插件
- 通常页面上会运行自定义脚本。检查这些脚本,确保它们经常 yield 到主线程,并将不太重要的回调包装在 requestIdleCallback 函数中
- 按页面卸载未使用的脚本(提示:使用 wp_deregister_script)。某些插件倾向于在每个页面上注入脚本,即使不需要该功能。
- 检查您的 Tag Manager 并删除未使用或不需要的标签。
- 使用精简的主题。通常"什么都做"的多用途主题往往有更多脚本
- 避免使用页面构建器,因为它们通常严重依赖 JavaScript 来向最终用户呈现页面
REACT / NextJS
React 的 hooks 和功能使减少 INP 处理时间成为可能:
使用 React 并发功能优先处理用户交互:
React 在版本 16 和 18 中引入的并发功能提供了 hooks 和机制来优化渲染,以获得更流畅的用户体验,特别是在输入期间。
useTransition&startTransition:将非关键更新标记为稍后渲染。这可以防止大型更新阻塞用户交互。useDeferredValue:将您的 UI 分为重要部分和不太关键的部分。React 可以中断不太关键部分的渲染,以获得更响应的体验。useOptimistic:在异步操作(如网络请求)进行期间显示临时的乐观状态。即使在数据获取期间,这也能保持 UI 的响应性。
用于数据获取的 Suspense(React 18+):
React 18 中的 Suspense 可用于改善 INP (Interaction to Next Paint),它允许浏览器优先处理用户交互并优化渲染。虽然 React 16 引入了用于代码分割的 Suspense,但 React 18 将此功能扩展到了数据获取。
- 在数据加载时显示 fallback 组件,如加载指示器。
- 数据到达后,React 恢复渲染被暂停的组件。
- Suspense 与 Concurrent React 中的可中断渲染相结合,优先处理用户交互。如果用户与被暂停的组件交互,React 会优先渲染该组件,保持响应性。
总的来说,这些功能协同工作,确保 React 优先处理用户交互,并在更新或数据获取期间避免阻塞 UI。
Stop debating in Jira.
Get a definitive answer on your performance issues. I deliver a granular breakdown of your critical rendering path.
- Definitive Answers
- Granular Breakdown
- Critical Path Analysis

