Files
2nd/10_Wiki/Topics/Frontend/반응형 윈도우 리사이즈(Resize) 이벤트 처리.md
T
2026-05-10 22:08:15 +09:00

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
Resize Event
ResizeObserver
window resize
윈도우 리사이즈
none A 0.9 applied
frontend
browser-events
performance
react
2026-05-10 pending
language framework
typescript 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)

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

🤖 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