Skip to content
This repository was archived by the owner on Nov 4, 2025. It is now read-only.

Commit 50b4961

Browse files
authored
fix: defer callback to prevent triggering resizeobserver loop limit (#72)
1 parent 7da201e commit 50b4961

File tree

2 files changed

+81
-5
lines changed

2 files changed

+81
-5
lines changed

src/util.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@ export function monitorResize(element: HTMLElement, callback: Function) {
2929
let prevHeight: number = null;
3030

3131
function onResize([{ target }]: ResizeObserverEntry[]) {
32+
if (!document.contains(target)) return;
3233
const { width, height } = target.getBoundingClientRect();
3334
const fixedWidth = Math.floor(width);
3435
const fixedHeight = Math.floor(height);
3536

3637
if (prevWidth !== fixedWidth || prevHeight !== fixedHeight) {
37-
callback({ width: fixedWidth, height: fixedHeight });
38+
// https://webkit.org/blog/9997/resizeobserver-in-webkit/
39+
requestAnimationFrame(() => {
40+
callback({ width: fixedWidth, height: fixedHeight });
41+
});
3842
}
3943

4044
prevWidth = fixedWidth;

tests/util.test.js

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
1-
import { isSamePoint } from '../src/util';
1+
import { isSamePoint, monitorResize } from '../src/util';
2+
import 'resize-observer-polyfill';
3+
4+
let observer;
5+
6+
// eslint-disable-next-line arrow-body-style
7+
jest.mock('resize-observer-polyfill', () => {
8+
return class ResizeObserverMock {
9+
constructor(onResize) {
10+
this.onResize = onResize;
11+
observer = this;
12+
}
13+
14+
observe(element) {
15+
this.element = element;
16+
}
17+
18+
disconnect() {
19+
this.element = null;
20+
this.onResize = null;
21+
}
22+
23+
triggerResize() {
24+
this.onResize([{ target: this.element }]);
25+
}
26+
};
27+
});
228

329
describe('util', () => {
430
describe('isSamePoint', () => {
@@ -25,16 +51,62 @@ describe('util', () => {
2551
),
2652
).toBeTruthy();
2753
expect(
28-
isSamePoint({ pageX: 0, pageY: 2, clientX: 3, clientY: 4 }, { clientX: 5, clientY: 4 }),
54+
isSamePoint(
55+
{ pageX: 0, pageY: 2, clientX: 3, clientY: 4 },
56+
{ clientX: 5, clientY: 4 },
57+
),
2958
).toBeFalsy();
3059
});
3160

3261
it('null should be false', () => {
33-
expect(isSamePoint({ pageX: 0, pageY: 2, clientX: 3, clientY: 4 }, null)).toBeFalsy();
34-
expect(isSamePoint(null, { pageX: 0, pageY: 2, clientX: 3, clientY: 4 })).toBeFalsy();
62+
expect(
63+
isSamePoint({ pageX: 0, pageY: 2, clientX: 3, clientY: 4 }, null),
64+
).toBeFalsy();
65+
expect(
66+
isSamePoint(null, { pageX: 0, pageY: 2, clientX: 3, clientY: 4 }),
67+
).toBeFalsy();
3568
});
3669
it('2 empty should be false', () => {
3770
expect(isSamePoint({}, {})).toBeFalsy();
3871
});
3972
});
73+
74+
describe('monitorResize', () => {
75+
let element;
76+
77+
beforeEach(() => {
78+
element = document.createElement('div');
79+
element.getBoundingClientRect = jest.fn().mockReturnValueOnce({
80+
width: 100,
81+
height: 100,
82+
});
83+
document.body.appendChild(element);
84+
jest.useFakeTimers();
85+
global.requestAnimationFrame = fn => {
86+
setTimeout(fn, 16);
87+
};
88+
});
89+
90+
afterEach(() => {
91+
if (element) element.remove();
92+
jest.useRealTimers();
93+
});
94+
95+
it('should defer callback to next frame', () => {
96+
const callback = jest.fn();
97+
monitorResize(element, callback);
98+
observer.triggerResize();
99+
jest.runAllTimers();
100+
expect(callback).toHaveBeenCalled();
101+
});
102+
103+
it('should skip calling if target is removed already', () => {
104+
const callback = jest.fn();
105+
monitorResize(element, callback);
106+
element.remove();
107+
observer.triggerResize();
108+
jest.runAllTimers();
109+
expect(callback).not.toHaveBeenCalled();
110+
});
111+
});
40112
});

0 commit comments

Comments
 (0)