INP 优化实战:别再只盯着 FID 了,这套组合拳把响应延迟压到 200ms 以内
INP 优化实战:别再只盯着 FID 了,这套组合拳把响应延迟压到 200ms 以内
导语
你的页面 LCP 已经压到 2.5s 了,CLS 也绿了,但用户还是觉得“卡”。问题很可能出在 INP 上——这个 2024 年 3 月正式接替 FID 的核心 Web 指标,衡量的是页面在整个生命周期内对点击、触摸、按键的响应速度。如果你的 INP 超过 200ms,哪怕页面“已经加载完”,用户也会觉得前端“不跟手”。
正文
问题:为什么 FID 退役了,INP 更难搞
FID 只看“第一次交互”的输入延迟,而 INP 覆盖整个页面生命周期的所有交互,取 P75 最差响应作为最终得分。也就是说,一个 500ms 的按钮点击、一次 800ms 的搜索联想,都可能把 INP 拉红。更麻烦的是,INP 只能用真实用户监控拿到,Lab 工具里经常测不出来。
根据 MDN 和 web.dev 的定义,INP 低于 200ms 为良好,200–500ms 需要改进,超过 500ms 响应较慢。多篇实战文章也提到:主线程阻塞、长任务、事件处理函数执行时间过长是 INP 三大杀手。
方案:四层优化策略
- 第一层:主线程空闲控制:把重计算拆成多个小于 50ms 的任务块,给浏览器喘息机会。
scheduler.postTask()、requestIdleCallback()都可以用,但更实用的是setTimeout和requestAnimationFrame分片。 - 第二层:长任务拆分:Chrome DevTools Performance 面板里超过 50ms 的任务就是 Long Tasks,它们是 INP 的头号杀手。用
performance.mark()和performance.measure()标出长任务来源,优先优化第三方脚本和统计埋点代码。 - 第三层:输入响应优化:不要在点击回调里同步做重计算或等待 fetch。改成“立即给视觉反馈 → 异步取数 → 再更新状态”的三段式。这符合 RAIL 模型里 100ms 内响应用户输入的规范。
- 第四层:第三方与运行时治理:广告、统计、客服浮窗这些第三方脚本经常偷偷吃掉主线程。用
async和defer控制加载时机,设置content-visibility: auto跳过视口外渲染。
代码:从“卡”到“跟手”的改造示例
改造前:
button.addEventListener("click", async () => { const data = await fetch("/api/search?q=" + keyword) const result = await data.json() renderList(result)})
改造后:
button.addEventListener("click", async () => { showLoadingSpinner() await new Promise(requestAnimationFrame) const data = await fetch("/api/search?q=" + keyword) const result = await data.json() renderList(result)})
这个改造的本质是:先把“我已经收到了”画出来,再把“我算完了”交给下一帧。INP 测的是“下一次绘制”的时间,不是“完整数据渲染”的时间。
结论:用 Lighthouse CI 把 INP 管起来
INP 是真实用户指标,Lab 工具只能辅助。建议用 Lighthouse CI 在每次提交时跑一遍,同时接真实用户监控抓线上 P75 值。优化优先级按这个顺序排:
- 主线程阻塞大于 50ms 的长任务
- 事件回调里同步 fetch 或重计算
- 未加
defer的第三方脚本 - 连续布局抖动导致的渲染延迟
如果你的 INP 还在 500ms 以上,先去看 Performance 面板里的 Long Tasks,那是最直接的线索。
收尾
本周先做一件事:打开 Chrome DevTools 的 Performance 面板,录制一次用户真实操作,数一下有多少个超过 50ms 的长任务。先解决最长的那个,INP 通常会立刻改善一截。
评论区
0 条评论
登录后可评论。