Впровадження Resize Observer для адаптивних компонентів веб-сайту
ResizeObserver стежить за змінами розміру конкретного DOM-елемента. Не viewport, а саме елемента. Це ключова відмінність від CSS media queries — тут адаптивність прив'язана до контейнера компонента, а не до ширини екрана.
Класичний випадок: компонент графіка займає то всю ширину сторінки, то третину. CSS media query цього не бачить. ResizeObserver — бачить.
Базове використання
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
// entry.contentRect — розміри контентної області
// entry.borderBoxSize — розміри з урахуванням border
// entry.contentBoxSize — без padding/border
// entry.devicePixelContentBoxSize — у фізичних пікселях
const { width, height } = entry.contentRect
console.log(`${entry.target.id}: ${width}x${height}`)
}
})
observer.observe(element)
observer.unobserve(element)
observer.disconnect()
Container Queries через JavaScript до нативної підтримки
Нативні CSS Container Queries (@container) підтримуються у сучасних браузерах, але для старих або для складної логіки — ResizeObserver:
function applyContainerBreakpoints(
element: HTMLElement,
breakpoints: Record<number, string>
): () => void {
const sortedBreakpoints = Object.entries(breakpoints)
.map(([w, cls]) => [Number(w), cls] as [number, string])
.sort(([a], [b]) => a - b)
const observer = new ResizeObserver(([entry]) => {
const width = entry.contentRect.width
// Прибрати всі класи брейкпоинтів
sortedBreakpoints.forEach(([, cls]) => element.classList.remove(cls))
// Додати підходящий
for (const [minWidth, cls] of sortedBreakpoints) {
if (width >= minWidth) element.classList.add(cls)
}
})
observer.observe(element)
return () => observer.disconnect()
}
// Використання:
applyContainerBreakpoints(cardEl, {
0: 'card--xs',
320: 'card--sm',
480: 'card--md',
640: 'card--lg',
})
Автоматичний resize канваса
function makeResponsiveCanvas(
canvas: HTMLCanvasElement,
draw: (ctx: CanvasRenderingContext2D, width: number, height: number) => void
): () => void {
const ctx = canvas.getContext('2d')!
const dpr = window.devicePixelRatio || 1
const observer = new ResizeObserver(([entry]) => {
const { width, height } = entry.contentRect
// Встановлюємо реальний розмір у пікселях
canvas.width = Math.round(width * dpr)
canvas.height = Math.round(height * dpr)
// CSS-розмір залишається попереднім
canvas.style.width = `${width}px`
canvas.style.height = `${height}px`
// Масштабуємо контекст для retina
ctx.scale(dpr, dpr)
draw(ctx, width, height)
})
observer.observe(canvas.parentElement ?? canvas)
return () => observer.disconnect()
}
React Hook
function useResizeObserver<T extends HTMLElement = HTMLDivElement>(): [
RefObject<T | null>,
DOMRectReadOnly | null,
] {
const ref = useRef<T>(null)
const [rect, setRect] = useState<DOMRectReadOnly | null>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new ResizeObserver(([entry]) => {
setRect(entry.contentRect)
})
observer.observe(el)
return () => observer.disconnect()
}, [])
return [ref, rect]
}
// Адаптивний компонент на основі хука:
function AdaptiveChart({ data }: { data: number[] }) {
const [ref, rect] = useResizeObserver<HTMLDivElement>()
const isCompact = (rect?.width ?? 0) < 400
return (
<div ref={ref} style={{ width: '100%' }}>
{isCompact ? (
<CompactChart data={data} width={rect?.width} />
) : (
<FullChart data={data} width={rect?.width} height={rect?.height} />
)}
</div>
)
}
Debounce для частих змін
ResizeObserver спрацьовує при кожній змінці розміру — при ресайзі вікна це може бути кілька разів у секунду. Для важких обчислень потрібен debounce:
function useResizeObserverDebounced<T extends HTMLElement>(
delay = 150
): [RefObject<T | null>, DOMRectReadOnly | null] {
const ref = useRef<T>(null)
const [rect, setRect] = useState<DOMRectReadOnly | null>(null)
const timerRef = useRef<ReturnType<typeof setTimeout>>()
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new ResizeObserver(([entry]) => {
clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => setRect(entry.contentRect), delay)
})
observer.observe(el)
return () => {
observer.disconnect()
clearTimeout(timerRef.current)
}
}, [delay])
return [ref, rect]
}
Що включено
Настройка ResizeObserver для потрібних компонентів, React хуки з debounce, адаптація компонентів по ширині контейнера (а не viewport), автоматичний resize канваса при необхідності.
Терміни: 0,5 дня.







