调整观察者大小 - 超出循环限制



大约两个月前,我们开始使用Rollbar来通知我们Web应用程序中的各种错误。

ResizeObserver loop limit exceeded

让我感到困惑的是,我们没有使用ResizeObserver,我已经调查了我认为可能是罪魁祸首的唯一插件,即:

奥蕾莉亚调整大小

但它似乎也没有使用ResizeObserver

同样令人困惑的是,这些错误消息自一月份以来一直在发生,但ResizeObserver支持最近才添加到Chrome 65中。

给我们此错误的浏览器版本是:

  • 铬:63.0.3239(超出调整大小观察器循环限制)
  • 铬:64.0.3282(超出调整大小观察器循环限制)
  • 边缘:14.14393(安全错误)
  • 边缘:15.15063(安全错误)

所以我想知道这是否可能是浏览器错误?或者也许是一个实际上与ResizeObserver无关的错误?

您可以安全地忽略此错误。

其中一位规范作者在评论中写了您的问题,但这不是答案,评论中也不清楚该答案确实是该线程中最重要的答案,并且让我可以轻松地在我们的哨兵日志中忽略它。

此错误意味着 ResizeObserver 无法在单个动画帧内传递所有观测值。它是良性的(您的网站不会中断)。– 亚历山大·托蒂克 4 月 15 日 3:14

在规范存储库中也存在一些与此相关的问题。

这是一个古老的问题,但它仍然可能对某人有所帮助。您可以通过将回调包装在requestAnimationFrame中来避免此错误。 例如:

const resizeObserver = new ResizeObserver(entries => {
// We wrap it in requestAnimationFrame to avoid this error - ResizeObserver loop limit exceeded
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) {
return;
}
// your code
});
});

如果您使用的是 Cypress 并且遇到此问题,则可以在 Cypress 中使用 support/index 中的以下代码安全地忽略它.js或 command.ts

const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/
Cypress.on('uncaught:exception', (err) => {
/* returning false here prevents Cypress from failing the test */
if (resizeObserverLoopErrRe.test(err.message)) {
return false
}
})

您可以在此处关注有关它的讨论。 由于赛普拉斯维护者自己提出了这个解决方案,所以我相信这样做是安全的。

我们遇到了同样的问题。我们发现Chrome扩展程序是罪魁祸首。具体来说,织机镀铬扩展导致了错误(或我们的代码与织机扩展的某些交互)。当我们禁用扩展程序时,我们的应用程序可以正常工作。

我建议禁用某些扩展/插件,以查看其中一个是否可能导致错误。

对于摩卡用户:

下面的代码段覆盖了 window.onerror 钩子摩卡安装并将错误转换为警告。 https://github.com/mochajs/mocha/blob/667e9a21c10649185e92b319006cea5eb8d61f31/browser-entry.js#L74

// ignore ResizeObserver loop limit exceeded
// this is ok in several scenarios according to 
// https://github.com/WICG/resize-observer/issues/38
before(() => {
// called before any tests are run
const e = window.onerror;
window.onerror = function(err) {
if(err === 'ResizeObserver loop limit exceeded') {
console.warn('Ignored: ResizeObserver loop limit exceeded');
return false;
} else {
return e(...arguments);
}
}
});

不确定有没有更好的方法..

该错误可能值得调查。它可以指示代码中可以修复的问题。

在我们的例子中,观察到的元素大小调整触发了页面上的更改,这再次导致第一个元素的大小调整,这再次触发了页面上的更改,这再次导致第一个元素的大小调整,...你知道这是怎么结束的。

从本质上讲,我们创建了一个无限循环,显然无法适应单个动画帧。我们通过使用setTimeout()在页面上保留更改来破坏它(尽管这并不完美,因为它可能会导致用户闪烁)。

因此,每当ResizeObserver loop limit exceeded现在出现在我们的哨兵中时,我们都会将其视为有用的提示,并试图找到问题的原因。

添加去抖动

new ResizeObserver(_.debounce(entries => {}, 200);

为我修复了此错误

赛普拉斯的单线解决方案。编辑文件支持/命令.js

Cypress.on(
'uncaught:exception',
(err) => !err.message.includes('ResizeObserver loop limit exceeded')
);

就我而言,由于window.addEventListener("resize"和 React 的React.useState,触发了"调整大小观察器 - 超出循环限制"的问题。

详细地说,我正在研究名为useWindowResize的钩子,其中用例是这样的const [windowWidth, windowHeight] = useWindowResize();

代码通过useEffect对窗口宽度/窗口高度更改做出反应。

React.useEffect(() => {
ViewportService.dynamicDimensionControlledBy(
"height",
{ windowWidth, windowHeight },
widgetModalRef.current,
{ bottom: chartTitleHeight },
false,
({ h }) => setWidgetHeight(h),
);
}, [windowWidth, windowHeight, widgetModalRef, chartTitleHeight]);

因此,任何浏览器窗口大小调整都会导致该问题。

我发现由于连接旧的javascript世界(DOM操作,浏览器的事件)和新的javascript世界(React)而导致的许多类似问题可以通过setTimeout来解决,但我会避免它,并在可能的情况下称之为反模式。

所以我的解决方法是将 setter 方法包装到setTimeout函数中。

React.useEffect(() => {
ViewportService.dynamicDimensionControlledBy(
"height",
{ windowWidth, windowHeight },
widgetModalRef.current,
{ bottom: chartTitleHeight },
false,
({ h }) => setTimeout(() => setWidgetHeight(h), 0),
);
}, [windowWidth, windowHeight, widgetModalRef, chartTitleHeight]);

我们在 Monaco Editor 中也遇到了这个问题,因为它在内部使用ResizeObserver

为了修复它,我们通过以下方式修补了原始 API:

class CalmResizeObserver extends ResizeObserver {
constructor(callback: ResizeObserverCallback) {
super((entries, observer) => {
requestAnimationFrame(() => {
callback(entries, observer);
});
});
}
}
win.ResizeObserver = CalmResizeObserver;

https://github1s.com/chromium/chromium/blob/master/third_party/blink/renderer/core/resize_observer/resize_observer_controller.cc#L44-L45 https://github1s.com/chromium/chromium/blob/master/third_party/blink/renderer/core/frame/local_frame_view.cc#L2211-L2212

查看源代码后,似乎在我的情况下,当调用 NotifyResizeObserver 函数并且没有注册的观察者时,问题浮出水面。

如果没有观察者,GatherObservation 函数将返回 4096 min_depth,在这种情况下,我们将收到"超出 ResizeObserver 循环限制"错误。

我解决这个问题的方法是让观察者生活在页面的整个生命周期中。

Reactjs

修复错误:超出调整观察器循环限制

的大小
useEffect(() => {
window.addEventListener('error', e => {enter code here
if (e.message === 'ResizeObserver loop limit exceeded' || e.message === 'Script error.') {
const resizeObserverErrDiv = document.getElementById(
'webpack-dev-server-client-overlay-div'
)
const resizeObserverErr = document.getElementById(
'webpack-dev-server-client-overlay'
)
if (resizeObserverErr) {
resizeObserverErr.setAttribute('style', 'display: none');
}
if (resizeObserverErrDiv) {
resizeObserverErrDiv.setAttribute('style', 'display: none');
}
}
})
}, [])

设法在 React 中为我们的错误记录器设置解决了这个问题。

Observer 错误传播到window.onerror错误处理程序,因此通过将原始window.onerror存储在 ref 中,可以将其替换为不会为此特定错误引发的自定义方法。允许其他错误正常传播。

请确保在useEffect清理中重新连接原始onerror

const defaultOnErrorFn = useRef(window.onerror);
useEffect(() => {
window.onerror = (...args) => {
if (args[0] === 'ResizeObserver loop limit exceeded') {
return true;
} else {
defaultOnErrorFn.current && defaultOnErrorFn.current(...args);
}
};
return () => {
window.onerror = defaultOnErrorFn.current;
};
}, []);

我遇到了柏树测试无法运行的问题。 我发现正确的方法是编辑 tsconfig.json 以针对新的 es6 版本,而不是处理异常,如下所示:

{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "../node_modules",
"target": "es5", --> old
"target": "es6", --> new
"types": ["cypress", "@testing-library/cypress"],
"sourceMap": true
},
"include": [
"**/*.ts"
]
}

就我而言,我只对元素宽度的变化感兴趣。但是,在 ResizeObserver 事件期间更改元素的高度会导致"超出 ResizeObserver 循环限制"错误。

为了抑制该错误,我在更改元素大小之前停止观察,并在渲染完成后再次开始观察。本质上:

const observer = new ResizeObserver(function (entries) {
observer.unobserve(element);
// ...manipulate the element...
setTimeout(function () {
observer.observe(element);
}, 0);
});

完整的解决方案在这里:https://gist.github.com/diachedelic/b026fdd168c8af8cd8ac5cb914e7b3cc。

在 Cypress/Jest/Mocha 中,您可以使用 polyfill 避免此错误:

npm install resize-observer-polyfill --save-dev 

将此添加到测试中:

global.ResizeObserver = require('resize-observer-polyfill');

最新更新