症状
警告文本解释了它的仁慈:ResizeObserver loop limit exceeded——浏览器在同一帧里跑了多轮观察传递,因为每轮都再次使布局失效,随后停止迭代以保护帧预算。视觉孪生是抖动:元素在两个尺寸间振荡,A 响应 B、B 又响应回来。经典肇因形态是回调里写被观察元素自己的 style——写入改变被观察尺寸,触发下一轮观察,环就闭合了。库把容器按内容自适应同时又观察同一容器,是变体。
诊断思路
先切换组件观察计算宽度的闪烁,可见振荡即确认反馈环而非良性积压;再审计所有 ResizeObserver 回调里的样式写入——写在被观察元素或其互耦伙伴上的每一笔都是环边;最后给回调插桩记录连续两次调用的尺寸,相同或交替即环签名,收敛则健康。
根因
ResizeObserver 回调在布局之后的同一帧执行。回调写入的样式使被观察元素布局失效时,浏览器调度下一轮观察;反馈迭代数轮后以 loop-limit 警告放弃,以保护帧预算。
修复与验证
三招常组合:把写入用 requestAnimationFrame 推出观察传递;加“值未变即跳过”的守卫让收敛终结循环;重构自适应结构——观察内部内容、给外层容器定尺寸,绝不观察自己正在写样式的元素。验证时调整尺寸的交互不再产生警告、元素一步收敛、连续 resize 期间帧率平稳。用 window.onerror 过滤全局压制警告会把未来每个组件的真无限布局环一起藏掉——修具体的环。
代码示例
ResizeObserver loop limit exceeded
const ro = new ResizeObserver(([entry]) => {
el.style.height = entry.contentRect.height * 2 + 'px';
// the write changes the observed size -> next observation -> loop
});
ro.observe(el);
const ro = new ResizeObserver(([entry]) => {
requestAnimationFrame(() => {
el.style.height = entry.contentRect.height * 2 + 'px';
});
});
let last = 0;
const ro = new ResizeObserver(([entry]) => {
const h = Math.round(entry.contentRect.height);
if (h === last) return; // converged: stop reacting
last = h;
el.style.height = h * 2 + 'px';
});