[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-10 22:08:15 +09:00
parent 21ac3ed255
commit 504fd5fb42
3011 changed files with 380280 additions and 206977 deletions
@@ -2,99 +2,221 @@
id: wiki-2026-0508-반응형-윈도우-리사이즈-resize-이벤트-처리
title: 반응형 윈도우 리사이즈(Resize) 이벤트 처리
category: 10_Wiki/Topics
status: needs_review
status: verified
canonical_id: self
aliases: [P-Reinforce-AUTO-DC2EFA]
aliases: [Resize Event, ResizeObserver, window resize, 윈도우 리사이즈]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
tags: [auto-reinforced]
verification_status: applied
tags: [frontend, browser-events, performance, react]
raw_sources: []
last_reinforced: 2026-04-20
github_commit: "[P-Reinforce] Continuous Worker - 반응형 윈도우 리사이즈(Resize) 이벤트 처리"
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: unspecified
framework: unspecified
language: typescript
framework: react
---
# [[반응형 윈도우 리사이즈(Resize) 이벤트 처리|반응형 윈도우 리사이즈(Resize) 이벤트 처리]]
# 반응형 윈도우 리사이즈(Resize) 이벤트 처리
## 📌 한 줄 통찰 (The Karpathy Summary)
> 윈도우 창 크기 조절 시 초당 수십 번씩 발생하는 `resize` 이벤트로 인한 UI 렌더링 병목 현상을 방지하기 위해, 디바운싱(Debouncing) 적용 및 이벤트 리스너 해제(Cleanup)를 통해 브라우저 과부하와 메모리 누수를 막는 성능 최적화 기법입니다.
## 한 줄
> **"매 resize 의 fire 매 cheap 매 listener 매 expensive — debounce 또는 ResizeObserver"**. window resize 매 60fps+ 의 fire — 매 naive listener 매 layout thrash. 매 modern (2026) 의 ResizeObserver 의 element-level + native browser-throttled API.
## 📖 구조화된 지식 (Synthesized Content)
**1. 리사이즈 이벤트의 성능 병목 현상** 브라우저 창 크기를 조절할 때, 브라우저는 아주 짧은 시간에 수십에서 수백 번의 `resize` 이벤트를 연속적으로 발생시킵니다. 만약 이 이벤트 핸들러 내부에서 React의 상태([[State|State]])를 업데이트하거나 무거운 연산을 수행하게 되면, 컴포넌트 트리가 매 틱마다 리렌더링을 시도하여 심각한 프레임 드랍과 브라우저 멈춤 현상(Bottleneck)이 발생합니다.
## 매 핵심
**2. 스로틀링(Throttling)과 디바운싱(Debouncing) 적용** 과도한 이벤트 실행을 제어하기 위해 핸들러 함수에 디바운싱이나 스로틀링을 적용해야 합니다.
### 매 두 종류 의 API
- **`window.addEventListener('resize', ...)`** — viewport-level, fires 매 every pixel change.
- **`ResizeObserver`** — element-level, browser-throttled, observes contentBox/borderBox.
- **디바운스(Debounce):** 사용자가 창 크기 조절을 멈춘 후 일정 시간(예: 200ms)이 지났을 때 마지막에 단 한 번만 리사이즈 로직(상태 업데이트 등)을 처리합니다. 레이아웃 재계산 비용이 큰 경우 가장 적합한 해결책입니다.
- **스로틀(Throttle):** 창 크기를 조절하는 중에도 실시간으로 부드러운 UI 업데이트가 필요할 때, 이벤트를 일정한 시간 간격(예: 16ms, 약 60FPS)으로만 실행되도록 강제합니다.
### 매 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).
**3. 이벤트 리스너 해제와 메모리 누수([[memory|memory]] Leak) 방지** React 컴포넌트 내부(주로 `useEffect` 훅)에서 `window.addEventListener('resize', handleResize)`를 등록했다면, **반드시 컴포넌트가 화면에서 사라질 때(Unmount) `window.removeEventListener`를 호출하는 클린업(Cleanup) 함수를 반환**해야 합니다. 이 정리 작업을 누락하면 컴포넌트가 파괴된 후에도 숨겨진 백그라운드에서 이벤트가 계속 수신되어, 시간이 지날수록 앱이 느려지고 결국 크래시를 유발하는 심각한 메모리 누수로 이어집니다.
### 매 응용
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.
**4. 외부 지식: ResizeObserver API 활용 (※ 외부 지식 참고)** _이 내용은 제공된 소스 외부의 지식입니다._ 최신 웹 개발에서는 전역 `window` 객체에 `resize` 이벤트를 거는 대신, DOM 요소 자체의 크기 변화만 독립적으로 감지하는 브라우저 내장 API인 `ResizeObserver`를 활용하는 추세입니다. 이를 사용하면 전체 윈도우가 아닌 특정 컴포넌트(예: 반응형 3D 캔버스나 대규모 데이터 그리드 영역)의 크기가 변할 때만 타겟팅하여 반응할 수 있어 성능과 모듈성 면에서 훨씬 효율적입니다.
## 💻 패턴
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
- **과거 데이터와의 충돌:** 자동화 엔진에 의해 매핑된 지식으로, 추후 정밀 검증 필요.
- **정책 변화:** Design & Experience 분야의 자동 자산화 수행.
### 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);
}
});
## 🔗 지식 연결 (Graph)
- **Related Topics:** Throttling & Debouncing, [[React Performance Optimization|React Performance Optimization]], Memory Leak Prevention (메모리 누수 방지), [[useEffect 클린업(Cleanup)|useEffect 클린업(Cleanup]]
- **Projects/Contexts:** 반응형 3D 캔버스(Three.js/R3F) 크기 최적화, [[실시간 데이터 대시보드 레이아웃 조절 시스템|실시간 데이터 대시보드 레이아웃 조절 시스템]]
- **Contradictions/Notes:** 단순히 윈도우 크기에 맞춰 CSS 요소의 크기나 배치를 조정하는 것이 목적이라면, 자바스크립트의 `resize` 이벤트를 아예 사용하지 않고 CSS의 미디어 쿼리(Media Queries)나 상대 단위(`vw`, `vh`, `100%`)를 사용하는 것이 렌더링 파이프라인 성능 측면에서 가장 비용이 낮고 완벽한 방법입니다. 자바스크립트 이벤트는 캔버스 렌더러 리사이즈나 복잡한 가상화 리스트(Virtualization) 재계산 등 CSS만으로 해결할 수 없는 경우에만 제한적으로 사용해야 합니다.
---
_Last updated: 2026-04-14_
---
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
**언제 이 지식을 쓰는가:**
- *(TODO)*
**언제 쓰면 안 되는가:**
- *(TODO)*
## 🧪 검증 상태 (Validation)
- **정보 상태:** needs_review
- **출처 신뢰도:** A
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
## 🧬 중복 검사 (Duplicate Check)
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
- **처리 방식:** UPDATE (자동 정규화)
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
## 🕓 변경 이력 (Changelog)
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|------|-----------|-----------|--------|
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
## 💻 코드 패턴 (Code Patterns)
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
```text
# TODO
const el = document.querySelector('.box')!;
ro.observe(el);
// cleanup
// ro.unobserve(el); ro.disconnect();
```
## 🤔 의사결정 기준 (Decision Criteria)
### 2. React hook: useResizeObserver
```typescript
import { useEffect, useState, useRef } from 'react';
**선택 A를 써야 할 때:**
- *(TODO)*
export function useResizeObserver<T extends HTMLElement>() {
const ref = useRef<T>(null);
const [size, setSize] = useState({ width: 0, height: 0 });
**선택 B를 써야 할 때:**
- *(TODO)*
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();
}, []);
**기본값:**
> *(TODO)*
return [ref, size] as const;
}
## ❌ 안티패턴 (Anti-Patterns)
// usage
function Card() {
const [ref, { width }] = useResizeObserver<HTMLDivElement>();
return <div ref={ref}>{width < 400 ? <Compact /> : <Full />}</div>;
}
```
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
### 3. Window resize with debounce
```typescript
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
```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<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)
```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 |