直接用 performanceobserver 监听 first-input 事件可准确获取 fid 值,因其从浏览器调度队列底层提取 duration,避免手动监听漏交互、时间戳受阻塞影响等问题,且仅首次有效输入触发一次。

直接用 PerformanceObserver 监听 first-input 类型事件,就能拿到真实用户场景下的 FID 值,无需手动计算时间差。它从浏览器底层输入调度队列中提取数据,比传统轮询或事件监听更准、更轻量。
为什么必须用 PerformanceObserver 而不是自己监听 click?
首次输入延迟不是“点击到执行”的间隔,而是用户触发交互(如 touchstart、mousedown、keydown)后,浏览器主线程空闲、真正开始处理该事件之间的时间差。这个过程发生在浏览器内部调度层,普通 JS 无法感知。PerformanceObserver 是唯一能直接读取该值的标准化 API。
- FID 数据只在首次有效输入时产生一次,且不可重复获取
- 手动监听 click/mousedown 会漏掉键盘输入、屏幕阅读器操作等非鼠标交互
- 自行计算时间戳(比如 Date.now() 记录点击时刻)会受任务排队、JS 执行阻塞影响,结果偏大、不具可比性
采集 FID 的标准写法
核心是订阅 first-input 类型,并在回调中提取 duration 字段:
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// entry.name === 'first-input' 是唯一标识
if (entry.entryType === 'first-input') {
const fid = entry.duration; // 单位:毫秒,就是你要的 FID 值
const startTime = entry.startTime; // 输入触发时间(相对于 navigationStart)
// 上报逻辑见下文
reportFID(fid, startTime, entry);
}
}
});
observer.observe({ entryTypes: ['first-input'] });
}
- 务必在页面早期(如 中或 script 标签加
defer)初始化 observer,避免错过首输事件 - 不需要主动调用
performance.getEntriesByType('first-input'),该方法始终返回空数组 —— FID 只能通过 observer 捕获 - entry.duration 已经是最终可用值,单位毫秒,无需再减去任何时间
上报到性能大盘的关键注意事项
上报不是简单发个请求,要兼顾成功率、数据质量与服务端可分析性:
- 用
navigator.sendBeacon()上报,确保页面卸载前也能发出。fetch 或 XHR 在 beforeunload 里大概率被中断 - 做轻量采样,例如只上报 10% 的 FID 数据(尤其高流量站点),避免打满日志管道
- 带上上下文:页面 URL、设备类型(mobile/desktop)、浏览器 UA、是否为 SPA 子路由、用户地区(如通过 IP 或 navigator.language 粗略判断)
- 过滤异常值:FID 超过 5000ms 的基本是误触或极端卡顿,可丢弃或单独打标;小于 1ms 的可能是测量噪声,建议设下限为 1
- 避免重复上报:一个页面生命周期内只会触发一次 first-input,observer 内部已天然去重,无需额外逻辑
如何验证 FID 采集是否生效?
打开 Chrome DevTools → Application → Clear storage → 刷新页面 → 点击任意可交互元素 → 回到 Console,执行:
performance.getEntriesByType('first-input')
如果返回空数组,说明 observer 未正确注册或太晚启动;如果返回一条 entry,检查其 duration 是否为合理数值(通常在 0–300ms 之间)。也可在 PerformanceObserver 回调里加 console.log 快速确认。










