</> HTML5Advent
ENFRESDEITPT

// apis

What is ResizeObserver? The element-level resize event, and the loop error everyone hits

The window resize event tells you nothing about a single element. ResizeObserver does. What it reports, the difference between content box and border box, and why "ResizeObserver loop completed with undelivered notifications" appears in your console.

A yellow tape measure stretched diagonally across a grey background, graduations from 21 to 42 with 30 and 40 printed in red

The resize event on window fires when the viewport changes. That is all it knows. An element can be resized by a flex container, a sidebar opening, a font finally loading or a textarea being dragged, and window will never hear about any of it.

ResizeObserver is the API that watches an element instead of the window.

What it actually reports

MDN puts it precisely: the interface "reports changes to the dimensions of an Element's content or border box, or the bounding box of an SVGElement".

Those two boxes are not the same thing, and picking the wrong one is a common source of off-by-a-few-pixels bugs. The content box is where content goes: the border box minus padding and border. The border box wraps content, padding and border together.

const observer = new ResizeObserver((entries) =&gt; {
  for (const entry of entries) {
    const { width, height } = entry.contentRect;
    entry.target.classList.toggle('is-narrow', width &lt; 400);
  }
});

observer.observe(document.querySelector('.card'));

The callback receives an array of entries because one observer can watch many elements, and several of them can change in the same frame. Reading entry.contentRect gives you the content box; entry.borderBoxSize and entry.contentBoxSize give you the boxes as arrays of dimension objects.

The error everyone meets: "loop completed with undelivered notifications"

Sooner or later this appears in the console, and it is worth understanding rather than silencing.

The problem it guards against is circular: your callback changes the size of something, that change triggers the observer again, which changes the size again. Left alone, the page would never settle.

// Boucle: le callback change la taille de ce qu il observe
const bad = new ResizeObserver(([entry]) =&gt; {
  entry.target.style.height = entry.contentRect.height + 1 + 'px';
});

The specification's answer, in MDN's words, is that "infinite loops from cyclic dependencies are addressed by only processing elements deeper in the DOM during each iteration". Anything that does not meet that condition is deferred to the next paint, and an error event fires on window with a fixed message:

ResizeObserver loop completed with undelivered notifications.

A person photographing themselves in a bathroom mirror facing another mirror, so the reflection repeats into the distance in ever smaller frames, the whole scene tinted blue-green

Here is the part that matters most, and MDN states it plainly: "this only prevents user-agent lockup, not the infinite loop itself". The browser is protecting itself, not fixing your code. The message is a symptom, and the loop is still there.

You may also see ResizeObserver loop limit exceeded, an older Chrome wording for the same family of problem. Different string, same thing to fix.

How to stop causing it

Do not write, in the callback, anything that changes the size of what you observe. Toggling a class that only changes colour is fine. Setting a height from a measured height is the textbook loop.

Observe the parent, style the child. Because the algorithm processes elements deeper in the DOM first, acting downwards rather than upwards keeps you on the right side of it.

Defer the write. Wrapping the mutation in requestAnimationFrame moves it out of the observation pass. Treat this as a workaround: if the dependency is genuinely circular, you have postponed it, not removed it.

When you do not need it at all

If all you want is to style a component according to its own width, CSS container queries do that in CSS, with no callback and no loop to worry about. Reach for ResizeObserver when you need the number itself in JavaScript: to redraw a canvas, lay out a chart, or recompute something that CSS cannot express.

And if you are watching for an element entering the viewport rather than changing size, that is a different observer entirely: see the Intersection Observer API.

The short version

ResizeObserver is the element-level answer to a window-level event. Choose the right box, never resize what you observe from inside the callback, and read the loop error as a real signal rather than console noise, because the browser only stopped itself from freezing - it did not fix the cycle.