Featured image of post 改善下次绘制 (INP) 交互的方法Featured image of post 改善下次绘制 (INP) 交互的方法

改善下次绘制 (INP) 交互的方法

了解 Core Web Vitals 与 Next Paint 的交互 (INP) 背后的指标,并了解如何防止 JavaScript 中的主线程阻塞。

介绍

在谷歌衡量网页用户体验的Core Web Vitals指标中,FID(首次输入延迟)已正式被**INP(与下一次绘制的交互)**取代。

FID 仅测量第一次用户交互的响应速度,而 INP 则评估页面访问整个生命周期中所有点击、敲击和键盘输入的响应能力。它记录交互和后续视觉更新(“下一次绘制”)之间的延迟。

本文详细介绍了 INP 的计算方式,并分享了优化 JavaScript 执行以防止主线程阻塞的策略。


1. 什么是 INP 以及如何测量?

INP 测量从用户触发单击、点击或按键到浏览器在屏幕上成功渲染更新的帧(下一步绘制)所需的时间。

INP 分数由三个不同的阶段组成:

[User Interaction]
   ├─► 1. Input Delay
   │      The time spent waiting for other tasks on the main thread to finish.
   ├─► 2. Processing Time
   │      The time taken to execute the JavaScript event listeners (e.g., onClick).
   └─► 3. Presentation Delay
          The time the browser spends recalculating layout and painting the frame.
[Frame Updated (Next Paint)]

为了提供良好的用户体验,网站的 INP 目标应为 200 毫秒或更短


2. 策略 1:分解长任务

INP 不佳的主要原因是“长任务”——占用主线程 50 毫秒或更长时间的 JavaScript 操作。当长任务运行时,用户输入被阻塞,导致明显的界面延迟。

为了避免阻塞主线程,应将大型任务分解为较小的块,以使浏览器将控制权交还给布局引擎。

未优化的代码(阻塞主线程)

function processHeavyData(items) {
  // Heavy calculations on a large array block the main thread for seconds
  for (const item of items) {
    doHeavyCalculation(item);
  }
}

优化代码(屈服于主线​​程)

使用现代 scheduler.yield() API(或像 setTimeout 这样的后备)允许浏览器在长时间运行的任务期间处理待处理的用户输入和绘制框架。

// Helper to yield control to the browser compositor
const yieldToMain = () => {
  if (globalThis.scheduler?.yield) {
    return scheduler.yield();
  }
  return new Promise(resolve => setTimeout(resolve, 0));
};

async function processHeavyDataInChunks(items) {
  let count = 0;
  for (const item of items) {
    doHeavyCalculation(item);
    count++;

    // Yield control every 50 iterations to allow rendering
    if (count % 50 === 0) {
      await yieldToMain();
    }
  }
}

3. 策略 2:最小化事件处理程序处理时间

在 React 或 Vue 等组件驱动框架中,在单击处理程序中触发多个状态更新可能会导致大量虚拟 DOM 协调,从而延长“处理时间”阶段。

最佳实践

  • 批量状态更新: 将更新分组在一起以最小化渲染周期。
  • **取消异步获取的优先级:**立即更新 UI 以显示加载指示器,然后在后台运行昂贵的数据获取或 API 突变。

4.策略3:减少呈现延迟

即使您的 JavaScript 快速完成,如果浏览器花费太长时间来绘制更新的布局,您的 INP 分数也会受到影响。

防止布局抖动

当 JavaScript 读取布局值(如 offsetHeight)并立即以强制浏览器重复重新计算页面布局的方式写入样式时,就会发生布局颠簸。

  • 不良做法:
    const width = element.offsetWidth; // Read
    element.style.width = (width + 10) + 'px'; // Write (forces immediate layout recalculation)
    
  • 最佳实践: 在写入操作之前批量读取操作,或使用 requestAnimationFrame 安排下一个渲染帧的样式更新。

使用 CSS 内容可见性

对于屏幕外元素(例如复杂的页脚或折叠网格),应用 content-visibility: auto 告诉浏览器跳过布局和渲染计算,直到用户将它们滚动到视图中。

.heavy-footer-section {
  content-visibility: auto;
  contain-intrinsic-size: 500px; /* Reserves layout space to prevent scrollbar jumping */
}

结论

优化 INP 对于搜索引擎可见性和减少移动设备上的用户流失至关重要。

  1. 使用 yieldsetTimeout 识别和分割长 JavaScript 任务(>50ms)。
  2. 最小化点击处理程序中的状态更改以立即显示加载状态。
  3. 避免布局抖动并使用 CSS 包含来降低渲染成本。

实施这些技术将使您的 Web 应用程序感觉快速并且能够响应用户输入。