ResizeObserver
Measure elements as they resize while every subscriber shares a single native ResizeObserver, with
callbacks batched into one frame so nothing thrashes.
How it works
getResizeObserver() hands out a single shared ResizeObserver. When the browser reports resizes,
the entries are buffered and flushed once inside a requestAnimationFrame, keeping only the latest entry
per element — so each subscriber is notified at most once per element per frame instead of several times.
That coalescing is what keeps many simultaneous observations off the resize-loop hot path.
The one option that changes the metrics, box, can’t be mixed on a single native observer, so the manager
keeps one shared observer per box mode: content-box and border-box each get their own. That box is
the only axis along which a second observer is ever created — everything else funnels through the same one.
Drag the bottom-right corner to resize ↘
import { getResizeObserver } from '@60fps/ui-utils';
// Every caller shares the same content-box observer.
const observer = getResizeObserver();
const onResize = (entry: ResizeObserverEntry) => {
console.log(entry.contentRect.width, entry.contentRect.height);
};
observer.subscribe(el, onResize);
// later…
observer.unsubscribe(el, onResize);
getResizeObserver(options?)
| Member | Type | Default | Description |
|---|---|---|---|
subscribe(el, cb) | (Element, cb) => void | — | Start observing an element; multiple callbacks per element are supported. |
unsubscribe(el, cb) | (Element, cb) => void | — | Remove one callback; the element is unobserved once its last callback is gone. |
observer | ResizeObserver | — | The underlying shared observer instance. |
options.box | 'content-box' | 'border-box' | 'content-box' | Which box metrics to observe. One shared observer is kept per box mode. |
Convenience hooks
useResizeObserver wraps the manager with framework lifecycle — pass a ref and a callback, and it
subscribes once the ref is attached and cleans up on unmount. The latest callback is always used, so an
inline arrow is fine.
Drag the bottom-right corner to resize ↘
Drag the bottom-right corner to resize ↘
import { useRef, useState } from 'react';
import { useResizeObserver } from '@60fps/ui-react/resize-observer';
function Measured() {
const ref = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState(0);
useResizeObserver(ref, (entry) => setWidth(entry.contentRect.width));
return (
<div ref={ref} className="resize overflow-auto">
{Math.round(width)}px
</div>
);
}
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { useResizeObserver } from '@60fps/ui-vue/resize-observer';
const box = useTemplateRef<HTMLElement>('box');
const width = ref(0);
useResizeObserver(box, (entry) => (width.value = entry.contentRect.width));
</script>
<template>
<div ref="box" class="resize overflow-auto">{{ Math.round(width) }}px</div>
</template>
useResizeObserver(ref, callback)
| Argument | Type | Default | Description |
|---|---|---|---|
ref | RefObject<Element> | Ref<Element> | — | The element to observe. Re-subscribes if it changes. |
callback | (entry, observer) => void | — | Called once per frame with the latest ResizeObserverEntry for the element. |