refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조
에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게 [공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류. 문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인). - Topic_Programming → Domain_Programming (내부 구조 보존) - Topic_Graphic → Domain_Design - Topic_Business → Domain_Product - Topic_General → Domain_General - _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning), Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing) - 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서) - 빈 폴더 정리 (memory/procedures) - 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
---
|
||||
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<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
|
||||
```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
|
||||
- 변형: [[ResizeObserver]] · [[IntersectionObserver]]
|
||||
- 응용: [[가상 스크롤]]
|
||||
- Adjacent: [[debounce / throttle]] · [[requestAnimationFrame]] · [[컨테이너 쿼리 (Container Queries)|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 |
|
||||
Reference in New Issue
Block a user