配了三年性能优化,今天才发现 INP 数值正常不代表用户看到的内容快——Interaction Contentful Paint 把这件事彻底测清楚了
你优化了一个搜索框,INP 从 480ms 降到了 180ms。数字很好看。但用户说:「点了没反应。」你拿着 INP 数据想不通——明明已经「达标」了,问题出在哪?
答案可能不是交互响应慢,而是内容出来得慢。INP 测量的是「从用户输入到下一帧绘制」的总时长,但它把渲染新内容的时间算进去了,你根本不知道慢的是交互处理还是浏览器把内容画出来这两件事。
Chrome 151 稳定版今年 7 月底发布,带来一个新的 PerformanceEntry 类型:interaction-contentful-paint。这个 API 专门测量用户交互之后,新的内容实际出现在被你交互修改过的那个 DOM 区域的时间。配合 Soft Navigations API 使用,你在 SPA 里终于能定位到是路由 A 的搜索结果渲染慢,还是路由 B 的筛选面板绘制慢,而不是笼笼统统一个跨会话的 INP 数字。
INP 说你没病,ICP 说你可能病得更隐蔽
INP 的全称是 Interaction to Next Paint,测量从用户点击、按键、触摸,到浏览器完成下一帧绘制的总时间。一个 400ms 的 INP 包含了:事件处理(可能是 50ms)+ 浏览器空闲(可能是 100ms)+ 渲染布局计算(可能是 150ms)+ 实际绘制新内容(可能是 100ms)。这个总数有用,但你只能看到总和,看不到各项的贡献。
Interaction Contentful Paint 专门补最后一个环节:实际内容出现在屏幕上花了多长时间。这个值独立于 INP 存在,而且它不是测量渲染管线的整体耗时,而是测量「你交互的区域里,新内容什么时候被画出来」。
Chrome 151 还给 interaction-contentful-paint 加了一个能力:即使新内容是通过异步 fetch 获取的,只要它最终被渲染到了你点击触发的那个区域,ICP 就会记录下来。比如你点击了搜索按钮,400ms 后 API 数据返回并插入 DOM,ICP 会记录这 400ms,而不是记录按钮点击的响应时间——这才是用户真正在意的东西:点了之后要等多久才能看到结果。
为什么它和 INP 是互补关系,不是替代关系
这是最容易混淆的地方。
INP 是一个全局指标,一个页面一整个会话就产出一个数值。它的目的是告诉你「这个页面整体上交互响应有没有问题」。一个 400ms 的 INP 说明有交互偏慢了,但你不知道是哪个环节拖了后腿。
ICP 是诊断工具,测量的是具体某一次交互里内容渲染的速度。INP 告诉你「有病」,ICP 告诉你「病灶在哪」。
Chrome 151 配合软导航一起理解价值更大。软导航(soft-navigation)让 Chrome 能够检测 SPA 里的路由切换,并给每次路由建立独立的计时起点——这意味着 LCP、CLS、INP 第一次能够被归因到具体路由,而不是从首页加载开始累积到用户会话结束。
现在的完整诊断链路是:
- INP 高 → 有交互慢了
- 软导航 + INP → 哪个路由的交互慢了
- ICP → 这次交互里,内容渲染花了多长时间
- LoAF → 具体是哪个脚本导致的主线程阻塞
四个放大镜同时工作,性能问题不再靠猜。
三行代码接入 ICP
不需要任何库,一个 PerformanceObserver 就能测:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// ICP 事件:具体某次交互后的内容绘制
if (entry.entryType === “interaction-contentful-paint”) {
console.log(“ICP:”, entry.startTime, “ms,交互ID:”, entry.interactionId);
}
// 软导航事件:检测到 SPA 路由切换
if (entry.entryType === “soft-navigation”) {
console.log(“路由:”, entry.name, “导航ID:”, entry.navigationId);
entry.getLargestInteractionContentfulPaint?.().then((icp) => {
if (icp) console.log(“路由渲染完成:”, icp.startTime, “ms”);
});
}
}
});
observer.observe({
type: [“interaction-contentful-paint”, “soft-navigation”],
buffered: true
});
如果想直接用 web-vitals 库的封装,第 6 版已经支持软导航:
import { onINP } from “web-vitals”;
// 开启软导航报告后,INP 会被归因到具体路由
onINP(({ value, attribution }) => {
console.log(“路由 INP:”, value, attribution.navigationType);
}, { reportSoftNavs: true });
三个必须知道的局限
第一,ICP 是 Chrome 独有的。 Safari 和 Firefox 目前没有跟进,这意味着你没法用它做跨浏览器性能基准对比。Google 也明确说过,暂时没有计划把软导航数据加入 CrUX(Chrome 用户体验报告),所以对 SEO 排名暂时没有直接影响。
第二,导航检测是启发式的,不是精确的。 Chrome 要求三个条件同时满足才会发出软导航事件:用户主动触发的交互、可见的 URL 变化(产生新的历史记录条目)、以及由此产生的可见绘制。这排除了 replaceState 改变筛选条件这类场景。如果你的 SPA 有特殊的路由实现,Chrome 的启发式判断可能和你的框架不一致。
第三,ICP 只测量「被交互修改的区域」里的新绘制。 如果一个交互改变了多个区域的布局,ICP 只能覆盖直接触发区域的内容,间接影响的部分不在范围内。
下一步:从哪开始
现在你在本地试这个 API 最简单。打开 Chrome 151,在控制台跑上面的 PerformanceObserver 代码,然后操作你的应用——点击按钮、触发搜索、打开弹窗——看 entry.startTime 的值,理解哪些交互的 ICP 是你业务上最关心的。
真正的工程价值在于:把 ICP 数据和你的业务事件关联起来。比如「结算按钮点击 → 订单确认面板渲染」的 ICP 中位数,就是你的结算流程真实用户体验。比起一个跨会话的综合 INP,这个数字对产品团队更有意义,也更容易推动优化优先级。
INP 告诉你哪个路由有问题,ICP 告诉你这个路由里哪一步渲染慢了。这两个数字放在一起,才是完整的 SPA 性能真相。
评论区
登录后可评论。