直接用 performanceobserver 捕获 inp 可行但需组合监听 event 类型 + 后处理,因 inp 本质是用户交互到下一帧渲染的总耗时,依赖 event timing api 并需 buffered:true、聚合 duration≥0 的条目取第98百分位,辅以 longtask 对齐定位卡顿根源。

直接用 PerformanceObserver 捕获 INP 是可行的,但不能靠监听单一事件类型“一键拿到”,它需要组合监听 + 后处理逻辑。INP 的本质是分析所有用户交互(点击、敲击、键盘输入等)从触发到下一次画面更新的完整耗时,而浏览器只通过 event 类型的 PerformanceEventTiming 条目暴露原始数据。
监听 event 类型获取原始交互数据
INP 依赖 Event Timing API,必须先启用该能力,并用 PerformanceObserver 监听 event 类型条目:
- 在页面早期(如
中或模块入口)调用performance.setResourceTimingBufferSize(200)并确保event类型已注册 - 使用以下代码启动监听(
buffered: true很关键,能捕获注册前已发生的交互):
理解 entry.duration 的构成与局限
entry.duration 表示该次交互从用户操作开始,到浏览器完成渲染(即下一帧绘制)的总时间,但它不拆解为输入/处理/呈现三段。实际中:
- 若
entry.duration ,说明该交互未阻塞主线程,也未触发重绘,通常可忽略 - 若值 ≥ 50ms,大概率存在长任务或强制同步布局,需进一步排查
- 单个高值不等于 INP —— INP 是全生命周期中第98百分位的值,需累积至少几十次交互后计算
手动聚合计算近似 INP 值
官方 web-vitals 库内部正是这样做的:收集所有 event 条目,过滤掉无效项(如 duration ≤ 0),保留 Top N 最长的,再取第98百分位。你可以轻量实现:
- 声明一个数组(如
inpCandidates = []),每次收到entry.duration > 0就 push 进去 - 限制数组长度(如最多存 100 个),避免内存膨胀
- 在页面卸载前(
beforeunload)或定时(如每 30 秒)按升序排序,取Math.floor(len * 0.98)索引位置的值作为当前 INP 估算
结合 longtask 定位卡顿根源
INP 高往往源于某次交互触发了长任务。可并行监听 longtask,与 event 条目做时间对齐:
- 监听
longtask类型,记录其startTime和duration - 当某个
event的startTime落在某次longtask时间范围内,基本可判定该长任务拖慢了这次交互 - 常见源头包括:未拆分的大批量 DOM 操作、同步计算密集型函数、未优化的第三方 SDK 初始化










