Files
2nd/10_Wiki/Topic_Programming/Frontend/반응형 윈도우 리사이즈(Resize) 이벤트 처리.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +09:00

6.8 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