--- id: wiki-2026-0508-반응형-윈도우-리사이즈-resize-이벤트-처리 title: 반응형 윈도우 리사이즈(Resize) 이벤트 처리 category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Resize Event, ResizeObserver, window resize, 윈도우 리사이즈] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [frontend, browser-events, performance, react] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: react --- # 반응형 윈도우 리사이즈(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). ### 매 응용 1. **Responsive breakpoint** — JS-driven layout. 2. **Canvas/Chart resize** — re-draw on size change. 3. **Virtualized list** — recalculate row count. 4. **Modal positioning** — re-center. ## 💻 패턴 ### 1. ResizeObserver (modern, preferred) ```typescript 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 ```typescript import { useEffect, useState, useRef } from 'react'; export function useResizeObserver() { const ref = useRef(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(); return
{width < 400 ? : }
; } ``` ### 3. Window resize with debounce ```typescript function debounce any>(fn: T, ms = 200): T { let timer: ReturnType | null = null; return ((...args: Parameters) => { 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 ```typescript 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 ```typescript function rafThrottle any>(fn: T): T { let raf = 0; let lastArgs: Parameters; return ((...args: Parameters) => { 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) ```typescript 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) ```typescript 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. - **`offsetWidth` in 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 |