PerformanceObserver 监听 longtask 是检测卡顿最直接方式,需注册 entryTypes: ['longtask'] 实时捕获 ≥50ms 任务;单个 ≥100ms 或连续两个 ≥50ms 且发生在用户点击后300ms内即触发轻量降级,iOS Safari 不支持时 fallback 至 requestAnimationFrame 检测长帧。

用 PerformanceObserver 监听 longtask 判断卡顿
卡顿的本质是主线程被长时间占用,浏览器无法及时响应用户操作。Performance API 中的 longtask 类型能捕获持续 ≥ 50ms 的任务(Chrome 68+、Firefox 71+、Safari 15.4+ 支持),这是比 FPS 或帧率更直接的卡顿信号。
不能依赖 performance.getEntriesByType('longtask') 主动轮询——它只返回已结束的任务,且页面加载后才开始记录;必须用 PerformanceObserver 实时监听:
- 注册时指定
{ entryTypes: ['longtask'] },确保首次调用前已启用 - 每个
longtaskentry 包含startTime、duration和attribution(可定位到具体脚本或框架) - 注意:iOS Safari 对
longtask支持不完整,需 fallback 到帧率估算(见下文)
如何定义“卡顿”并触发降级逻辑
单个 longtask ≥ 100ms 或连续两个 ≥ 50ms 就该警惕——这已超出用户可感知的响应阈值(100ms 内无反馈即觉卡顿)。但不能一卡就降级,得结合上下文:
- 若卡顿发生在用户点击后 300ms 内,优先标记为交互阻塞,立即执行降级(如关闭动画、隐藏非核心组件)
- 若卡顿出现在空闲期(如滚动停止后 1s),可能是后台任务,仅记录不干预
- 降级动作本身要轻量:
requestIdleCallback包裹、避免触发重排,否则雪上加霜
示例判断逻辑:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.duration >= 100 && isUserInteractionActive()) {
enterDegradationMode();
}
});
});
observer.observe({ entryTypes: ['longtask'] });
没有 longtask 时怎么兜底检测卡顿
老旧 Android WebView、部分 iOS 版本不支持 longtask,此时只能靠帧率间接推断。关键不是算平均 FPS,而是抓「长帧」:
- 用
requestAnimationFrame记录每帧耗时,连续 2 帧 > 16ms(即低于 60fps)即预警 - 配合
performance.now()精确计时,避免Date.now()被系统时钟调整干扰 - 注意:滚动中帧率天然波动大,应过滤掉
scroll事件活跃期的判断
这种兜底方案误差较大,但至少能在不支持 longtask 的环境里给出基础卡顿信号。
降级模式里最容易被忽略的细节
降级不是简单关功能,而是有状态切换和恢复机制:
- 必须记录降级触发时间与原因(
entry.attribution?.container?.src或帧率数据),否则无法回溯优化 - 降级后不能永久生效——需在后续 3 秒内无新 longtask 才自动恢复,否则用户操作会持续受限
- 上报数据时,要把降级状态(on/off)、触发原因、持续时长一并发送,否则监控系统看不出策略是否有效
真正难的不是检测卡顿,而是让降级既及时又克制:太激进,用户觉得功能残缺;太保守,卡顿照旧。这个平衡点得靠真实设备上的长周期数据来校准。

















