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 폴더 제거.
This commit is contained in:
Antigravity Agent
2026-07-05 00:33:48 +09:00
parent 1cfd3bbb56
commit 9148c358d0
6455 changed files with 1 additions and 86875 deletions
@@ -0,0 +1,215 @@
---
id: wiki-2026-0508-layout-thrashing
title: Layout Thrashing
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Forced Synchronous Layout, Reflow Thrashing, FSL]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [web-performance, dom, reflow, browser, javascript]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: javascript
framework: vanilla/fastdom
---
# Layout Thrashing
## 매 한 줄
> **"매 layout thrashing = read → write → read → write 반복으로 브라우저가 매번 reflow를 강제로 동기 실행하는 안티 패턴"**. JS 한 frame 안에서 DOM 측정 (offsetHeight 등)과 mutation (style 변경)을 번갈아 하면 매 read마다 forced synchronous layout이 발생해 60fps가 붕괴된다. 해결은 read와 write를 batch + rAF로 분리.
## 매 핵심
### 매 왜 발생하나
- 브라우저는 효율을 위해 style/layout 계산을 **async + batched**로 처리.
- 하지만 layout-dependent property를 JS가 읽으면 (`offsetWidth`, `getBoundingClientRect`) 즉시 정확한 값을 줘야 하므로 pending mutation 전부를 동기 flush — **forced sync layout**.
- write → read를 반복하면 매 iteration마다 flush → O(n) reflow.
### 매 trigger되는 read 속성
- `offsetTop/Left/Width/Height`, `scrollTop/Left/Width/Height`, `clientTop/Left/Width/Height`.
- `getComputedStyle()`, `getBoundingClientRect()`.
- `innerText` (computed style 읽음).
- `focus()` (scroll 발생 가능).
### 매 trigger되는 write
- 어떤 layout 영향 style 변경: `el.style.width`, class 변경, DOM insert/remove.
- `setAttribute` (size/position 영향).
### 매 해결 원칙
1. **Read 모두 먼저**, 그다음 **write 모두**.
2. **rAF**: 측정은 현재 frame, 변경은 다음 frame.
3. **FastDOM** 같은 scheduler.
4. **CSS Containment** (`contain: layout`) — reflow 범위 격리.
5. **Transform/opacity** — composite-only, layout 안 발생.
### 매 응용
1. List virtualization.
2. Drag & drop.
3. Sticky / parallax.
4. Animation.
5. Resize observer 응답.
## 💻 패턴
### Anti-pattern — read/write 인터리브
```js
// ❌ N번 forced sync layout
const items = document.querySelectorAll('.item');
items.forEach(el => {
const w = el.offsetWidth; // read (flush!)
el.style.width = (w * 1.1) + 'px'; // write (invalidate)
});
```
### Fix — batch read 후 batch write
```js
// ✅ 1번만 layout 계산
const items = document.querySelectorAll('.item');
const widths = [];
for (const el of items) widths.push(el.offsetWidth); // all reads
for (let i = 0; i < items.length; i++) {
items[i].style.width = (widths[i] * 1.1) + 'px'; // all writes
}
```
### requestAnimationFrame + double-buffer
```js
function resizeAll() {
const items = [...document.querySelectorAll('.item')];
// measure phase
const widths = items.map(el => el.offsetWidth);
// mutate phase — 다음 frame
requestAnimationFrame(() => {
items.forEach((el, i) => {
el.style.width = (widths[i] * 1.1) + 'px';
});
});
}
```
### FastDOM 스타일 scheduler
```js
const reads = [];
const writes = [];
let scheduled = false;
function schedule() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
while (reads.length) reads.shift()();
while (writes.length) writes.shift()();
scheduled = false;
});
}
export const fastdom = {
measure(fn) { reads.push(fn); schedule(); },
mutate(fn) { writes.push(fn); schedule(); },
};
// usage
fastdom.measure(() => {
const w = el.offsetWidth;
fastdom.mutate(() => { el.style.width = (w * 1.1) + 'px'; });
});
```
### ResizeObserver — read/write 분리
```js
const ro = new ResizeObserver(entries => {
// entries already has measurements — read 안 해도 됨
const updates = entries.map(e => ({
el: e.target,
height: e.contentRect.height,
}));
requestAnimationFrame(() => {
for (const u of updates) u.el.style.setProperty('--h', u.height + 'px');
});
});
ro.observe(document.querySelector('#panel'));
```
### React — useLayoutEffect로 측정·변경 분리
```tsx
import { useLayoutEffect, useRef, useState } from "react";
function AutoFit({ children }) {
const ref = useRef<HTMLDivElement>(null);
const [w, setW] = useState(0);
useLayoutEffect(() => {
// 측정만
const next = ref.current!.offsetWidth;
setW(next); // 변경은 React commit이 batch
}, []);
return <div ref={ref} style={{ minWidth: w }}>{children}</div>;
}
```
### CSS Containment — reflow 범위 격리
```css
.card {
contain: layout style paint; /* 내부 변화가 외부 reflow 안 일으킴 */
}
.list-item {
contain: layout;
content-visibility: auto; /* 보이지 않으면 layout 스킵 */
}
```
### Performance 측정 — DevTools Performance API
```js
performance.mark('measure-start');
const w = el.offsetWidth;
performance.mark('measure-end');
performance.measure('forced-layout', 'measure-start', 'measure-end');
// DevTools Performance tab에서 "Recalculate Style" / "Layout" 보라색 막대 확인
```
### Transform 사용으로 layout 회피
```js
// ❌ left/top — layout 발생
el.style.left = x + 'px'; el.style.top = y + 'px';
// ✅ transform — composite-only
el.style.transform = `translate3d(${x}px, ${y}px, 0)`;
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| 단순 batch | read 먼저 → write 나중 (one pass) |
| 여러 모듈이 DOM 만짐 | FastDOM 또는 자체 scheduler |
| Animation | rAF + transform/opacity |
| 큰 리스트 | virtualization + content-visibility |
| 격리된 위젯 | `contain: layout style` |
**기본값**: 모든 hot path에서 read/write 분리 + rAF + transform 우선.
## 🔗 Graph
- 부모: [[Web-Performance]]
- 변형: [[Forced-Synchronous-Layout]], [[Reflow]], [[Repaint]]
- 응용: [[Drag-and-Drop]]
- Adjacent: [[requestAnimationFrame]], [[ResizeObserver]], [[Core Web Vitals Optimization (INP, LCP, CLS)|Core-Web-Vitals]]
## 🤖 LLM 활용
**언제**: 코드에서 read/write interleave 탐지, FastDOM-style 리팩터 제안, Performance trace 해석.
**언제 X**: 실제 frame budget 측정 — 디바이스/페이지 의존, DevTools 직접.
## ❌ 안티패턴
- **루프 안에서 offsetX 호출**: 가장 흔한 thrashing.
- **jQuery `.css()` 연쇄**: 내부에서 measure 트리거.
- **window.scroll listener에서 read+write**: scroll 매 이벤트마다 layout.
- **MutationObserver 콜백에서 sync read**: 배치 의미 사라짐.
- **Transform 가능한데 left/top 사용**: 불필요한 layout.
## 🧪 검증 / 중복
- Verified (Chrome DevTools docs, web.dev "Avoid large, complex layouts" 2026, Wilson Page FastDOM).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — FastDOM, useLayoutEffect, CSS containment 추가 |