getCLS()默认只返回页面生命周期结束后的稳定累计值,且忽略用户交互后500ms内的偏移,因此无法捕获AJAX、水合渲染等异步更新引发的抖动;必须启用reportAllChanges: true和includeSources: true才能实时获取含DOM源节点的每次偏移片段。

CLS 无法在 HTML 层面“统计”,它必须由浏览器运行时采集;异步更新(如 AJAX、Vue/React 渲染、Nuxt 客户端水合)引发的布局偏移,需用 getCLS 配合 reportAllChanges: true 和 includeSources: true 才能捕获真实片段,否则默认会漏掉 90% 的动态偏移。
为什么直接写 getCLS() 拿不到异步更新的偏移?
默认行为下,getCLS 仅上报「稳定后」的累计值,并主动忽略用户交互后 500ms 内发生的位移——而绝大多数异步更新(比如点击按钮加载评论、滚动触发动态广告、路由切换后组件挂载)都发生在这个时间窗口内。
- 不加配置时,
getCLS({ reportAllChanges: false })只返回最终浮点数,无法定位哪次fetch或setState触发了抖动 -
includeSources: false(默认)时,sources字段为空,你根本看不到是哪个<div id="comments"> 在插入时把下面的按钮顶下去了 <li>单页应用中,路由跳转后的偏移常被归入前一个页面的 CLS 总和,造成归因错误</li> <h3>必须启用的两个关键参数</h3> <p>要让 <code>getCLS真正反映异步行为的影响,这两项必须显式开启:-
reportAllChanges: true:每次 layout shift 发生时都触发回调,而不是只等页面生命周期结束才上报。你能拿到每个抖动的时间戳、value、以及是否发生在用户交互后 -
includeSources: true:使返回对象包含sources数组,其中每个条目都有node(DOM 节点引用)、currentRect和previousRect,可直接比对宽高/位置变化
示例用法:
立即学习“前端免费学习笔记(深入)”;
import { getCLS } from 'web-vitals'; getCLS((metric) => { if (metric.value > 0.002) { console.log('偏移源:', metric.sources?.[0]?.node?.tagName); console.log('偏移量:', metric.value); console.log('发生时间:', metric.startTime); } }, { reportAllChanges: true, includeSources: true });如何区分“真偏移”和“假偏移”?
异步更新中很多位移是用户预期内的(比如展开折叠面板),它们不该计入 CLS,但容易被误报:
- 用户点击后 500ms 内发生的位移,默认不计入 CLS —— 但如果你用了
reportAllChanges: true,它仍会出现在回调里,需手动过滤:metric.delta -
transform或opacity动画不会触发 layout,因此不产生 CLS;但若动画过程中 JS 修改了height或top,就会突然计入 - 第三方脚本(如广告 SDK)注入的节点,其
node可能为#document或iframe,这时要看metric.entries[0].containerRect定位父容器
生产环境必须补的三件事
光靠
getCLS回调还不够,真实场景中容易漏掉长周期偏移或归因失败:- SPA 中每次路由切换后,必须手动调用
reset()或监听navigation事件,否则新页面的偏移会累加进旧页面指标 - 上报时附带上下文:比如当前 URL、触发动作(
fetch /api/comments)、框架状态(isClientHydrated: true),否则无法反查是哪个 API 导致了抖动 - 用 Chrome DevTools 的
performance.getEntriesByType('layout-shift')手动验证:如果这里能看到条目,但getCLS没回调,说明参数没开对,或脚本加载太晚
最常被忽略的是:CLS 值低 ≠ 没抖动,它只统计“非用户触发”的意外位移;而很多异步更新的抖动,恰恰卡在“用户刚点完、JS 还没执行完”的灰色区间里——这时候得靠
reportAllChanges把它揪出来。 -



















