CLS超过0.1应立即报警,因其直接导致视觉抖动、误点和表单丢失;需用PerformanceObserver监听layout-shift事件,累加所有hadRecentInput为false的entry.value,并在页面隐藏时通过navigator.sendBeacon可靠上报。

CLS 超过 0.1 就该报警,不是等用户投诉才查 —— 它直接对应视觉抖动、误点、表单丢失等真实体验问题,而且 PerformanceObserver 在现代浏览器里开箱即用,根本不用等第三方 SDK。
用 PerformanceObserver 监听 layout-shift 类型事件
CLS 不是单次测量值,而是整个页面生命周期内所有布局偏移分数的累加。浏览器原生通过 PerformanceObserver 提供实时捕获能力,比手动计算靠谱得多。
- 必须监听
'layout-shift'类型,不是'largest-contentful-paint'或其他 - 需在页面早期(比如
<head>内或DOMContentLoaded前)注册,否则会漏掉首屏前的偏移 -
entry.value是本次偏移的分数,entry.sources可定位到具体 DOM 节点(但兼容性有限,Chrome 84+ 支持) - 要累加所有 entry 的
value,不能只取最后一次
const clsEntries = [];
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsEntries.push(entry);
}
}
}).observe({ type: 'layout-shift', buffered: true });
为什么 entry.hadRecentInput 必须为 false
CLS 只统计「非用户主动触发」的意外偏移。比如用户刚点完按钮,紧接着广告插入导致内容下移,这个偏移不算进 CLS —— 因为 hadRecentInput 为 true。这是规范硬性要求,不是可选项。
- 忽略它会导致上报值虚高,和 Lighthouse、Chrome UX Report 数据对不上
- 移动端尤其容易误判:触摸开始(touchstart)到 touchend 之间都算「recent input」,窗口约 500ms
- 如果业务需要监控「交互后偏移」(如弹窗关闭时下方内容跳动),得单独监听并打标,别混进 CLS 总分
如何把 CLS 值可靠上报到后端
页面卸载前必须完成上报,否则数据丢失。但 fetch(..., { keepalive: true }) 在部分安卓 WebView 和旧版 Safari 中不可靠;navigator.sendBeacon 是更稳的选择。
立即学习“前端免费学习笔记(深入)”;
- 上报时机选在
visibilitychange且document.visibilityState === 'hidden',比beforeunload更早更可控 - payload 至少包含
cls(累加值)、url、navigationType(reload / back_forward / navigate) - 避免在 observer 回调里直接发请求 —— 频繁偏移会触发大量并发请求,改用节流 + 卸载前统一发
- 服务端收到后应按 URL + 设备类型聚合,单独看「首页 CLS 中位数」比全站平均值更有诊断价值
let clsScore = 0;
// ... 在 PerformanceObserver 回调中累加 clsScore
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
const data = JSON.stringify({ eventType: 'cls', cls: clsScore, url: location.href });
navigator.sendBeacon('/api/monitor', data);
}
});
容易被忽略的两个关键点
一是动态内容插入后没预留空间,比如广告位用 height: 0 + opacity: 0 占位,但没设 overflow: hidden,加载时仍会触发重排;二是字体加载完成前用系统字体撑开的高度,和自定义字体实际高度不一致 —— 这种偏移不会出现在 DevTools 的 Layout Shifts 面板里,但会被 PerformanceObserver 捕获。两者都会拉高 CLS,但排查路径完全不同。



















