6.9 KiB
6.9 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | tags | raw_sources | last_reinforced | github_commit | tech_stack | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wiki-2026-0508-반응형-윈도우-리사이즈-resize-이벤트-처리 | 반응형 윈도우 리사이즈(Resize) 이벤트 처리 | 10_Wiki/Topics | verified | self |
|
none | A | 0.9 | applied |
|
2026-05-10 | pending |
|
반응형 윈도우 리사이즈(Resize) 이벤트 처리
매 한 줄
"매 resize 의 fire 매 cheap 매 listener 매 expensive — debounce 또는 ResizeObserver". window resize 매 60fps+ 의 fire — 매 naive listener 매 layout thrash. 매 modern (2026) 의 ResizeObserver 의 element-level + native browser-throttled API.
매 핵심
매 두 종류 의 API
window.addEventListener('resize', ...)— viewport-level, fires 매 every pixel change.ResizeObserver— element-level, browser-throttled, observes contentBox/borderBox.
매 throttle vs debounce
- Debounce — fire only N ms 후 last event (e.g., resize stops).
- Throttle — fire at most every N ms (continuous fire).
- Resize: 보통 debounce (final size 만 needed) 또는 throttle (live preview).
매 응용
- Responsive breakpoint — JS-driven layout.
- Canvas/Chart resize — re-draw on size change.
- Virtualized list — recalculate row count.
- Modal positioning — re-center.
💻 패턴
1. ResizeObserver (modern, preferred)
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log('resized:', width, height);
}
});
const el = document.querySelector('.box')!;
ro.observe(el);
// cleanup
// ro.unobserve(el); ro.disconnect();
2. React hook: useResizeObserver
import { useEffect, useState, useRef } from 'react';
export function useResizeObserver<T extends HTMLElement>() {
const ref = useRef<T>(null);
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
if (!ref.current) return;
const ro = new ResizeObserver(([entry]) => {
const { width, height } = entry.contentRect;
setSize({ width, height });
});
ro.observe(ref.current);
return () => ro.disconnect();
}, []);
return [ref, size] as const;
}
// usage
function Card() {
const [ref, { width }] = useResizeObserver<HTMLDivElement>();
return <div ref={ref}>{width < 400 ? <Compact /> : <Full />}</div>;
}
3. Window resize with debounce
function debounce<T extends (...args: any[]) => any>(fn: T, ms = 200): T {
let timer: ReturnType<typeof setTimeout> | null = null;
return ((...args: Parameters<T>) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
}) as T;
}
const onResize = debounce(() => {
console.log('done resizing:', window.innerWidth);
}, 200);
window.addEventListener('resize', onResize);
4. React useWindowSize hook
import { useEffect, useState } from 'react';
export function useWindowSize() {
const [size, setSize] = useState({
width: typeof window === 'undefined' ? 0 : window.innerWidth,
height: typeof window === 'undefined' ? 0 : window.innerHeight,
});
useEffect(() => {
let raf = 0;
const onResize = () => {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
setSize({ width: window.innerWidth, height: window.innerHeight });
});
};
window.addEventListener('resize', onResize, { passive: true });
return () => {
window.removeEventListener('resize', onResize);
cancelAnimationFrame(raf);
};
}, []);
return size;
}
5. Throttle with rAF
function rafThrottle<T extends (...args: any[]) => any>(fn: T): T {
let raf = 0;
let lastArgs: Parameters<T>;
return ((...args: Parameters<T>) => {
lastArgs = args;
if (raf) return;
raf = requestAnimationFrame(() => {
fn(...lastArgs);
raf = 0;
});
}) as T;
}
window.addEventListener('resize', rafThrottle(() => {
// runs at most once per frame
redrawCanvas();
}));
6. matchMedia (breakpoint detection)
const mql = window.matchMedia('(min-width: 768px)');
function onChange(e: MediaQueryListEvent) {
console.log('is desktop:', e.matches);
}
mql.addEventListener('change', onChange);
console.log('initial desktop:', mql.matches);
7. Canvas resize (HiDPI handling)
function fitCanvasToDPR(canvas: HTMLCanvasElement) {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
const ctx = canvas.getContext('2d')!;
ctx.scale(dpr, dpr);
return ctx;
}
const ro = new ResizeObserver(([entry]) => {
const canvas = entry.target as HTMLCanvasElement;
fitCanvasToDPR(canvas);
draw(canvas.getContext('2d')!);
});
ro.observe(canvas);
매 결정 기준
| 상황 | Approach |
|---|---|
| Element-level size watch | ResizeObserver (browser-throttled, no manual debounce). |
| Window-level (viewport) | window.addEventListener('resize') + debounce. |
| Smooth visual feedback during drag | requestAnimationFrame throttle. |
| Breakpoint change only | matchMedia (fires on threshold cross only). |
| Canvas / chart redraw | ResizeObserver + DPR scaling. |
기본값: ResizeObserver 의 element-level. matchMedia 의 breakpoint. window resize 의 only viewport-wide concerns + debounce 200ms.
🔗 Graph
- 부모: 브라우저 이벤트 모델 · 반응형 디자인 (Responsive Design)
- 변형: ResizeObserver · IntersectionObserver · matchMedia
- 응용: Canvas 렌더링 · 가상 스크롤 · 차트 라이브러리
- Adjacent: debounce / throttle · requestAnimationFrame · Container Queries
🤖 LLM 활용
언제: dynamic layout, canvas/chart, virtualized lists, modal repositioning, JS-driven breakpoint. 언제 X: pure CSS responsive 매 sufficient — JS resize handler 매 unneeded.
❌ 안티패턴
- No debounce/throttle on window.resize: 60+ fires/sec 매 layout thrash.
offsetWidthin resize handler without rAF: forced sync layout.- Forgetting cleanup: memory leak (especially in React without
removeEventListener). - Polling
window.innerWidth: use event-driven approach. - Heavy work synchronously: canvas full redraw 매 each event — defer/batch.
🧪 검증 / 중복
- Verified (MDN ResizeObserver 2026, web.dev resize patterns, React hooks docs).
- 신뢰도 A.
🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full content with ResizeObserver/debounce/rAF/matchMedia patterns |