Stop guessing the header height: measure it with ResizeObserver
A wrapping navigation, late webfonts and changing content all break constants written into CSS. A ResizeObserver watches the element and writes the measured value back into a variable.
How tall is the header? Does the navigation wrap? How wide should the table of contents be? CSS can approximate all of it and guarantees none of it. The values also move: webfonts land, windows resize, content changes length.
The window resize event is not enough
window.addEventListener('resize') covers exactly one kind of change: viewport size. It misses the rest, including line-height shifts after a font loads, a details element opening, and content arriving asynchronously during the same resize. Worse, it fires on every frame while dragging, and reading getBoundingClientRect() in that handler interleaves with style writes and forces synchronous layout.
Observe the element itself
A ResizeObserver watches an element. Any size change fires the callback, regardless of the cause:
const header = document.querySelector('[data-site-header]');
if (header) {
const observer = new ResizeObserver(() => {
const height = Math.round(header.getBoundingClientRect().height);
document.documentElement.style.setProperty('--anchor-offset', height + 16 + 'px');
});
observer.observe(header);
}
The callback only writes; it never reads another element. If you change something that affects the size of an observed element, the browser raises ResizeObserver loop completed with undelivered notifications, which is the platform telling you that you built a loop.
Two practical details
- Round before writing. Sub-pixel heights jitter on every callback, which makes
--anchor-offsetchange dozens of times a second and the anchor compensation jitter with it. Round first, then skip writes inside the same value. - Skip browsers without support. Every use here is compensation: the page works without it. No polyfill, and no error report when the API is missing.
When not to reach for it
If a size can be expressed in CSS, express it there: height: auto, aspect-ratio, clamp(), and grid auto-placement all exist. Measuring in JavaScript and writing the number back imitates what CSS already does, with an extra layout round trip attached. ResizeObserver earns its place for cross-element relationships CSS cannot express: a header height driving an anchor offset, a content width deciding whether the rail table of contents appears.
If you can measure it, do not guess. If CSS can compute it, do not measure.

Comments
…