9148c358d0
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 폴더 제거.
6.8 KiB
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-layout-thrashing | Layout Thrashing | 10_Wiki/Topics | verified | self |
|
none | A | 0.9 | applied |
|
2026-05-10 | pending |
|
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 영향).
매 해결 원칙
- Read 모두 먼저, 그다음 write 모두.
- rAF: 측정은 현재 frame, 변경은 다음 frame.
- FastDOM 같은 scheduler.
- CSS Containment (
contain: layout) — reflow 범위 격리. - Transform/opacity — composite-only, layout 안 발생.
매 응용
- List virtualization.
- Drag & drop.
- Sticky / parallax.
- Animation.
- Resize observer 응답.
💻 패턴
Anti-pattern — read/write 인터리브
// ❌ 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
// ✅ 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
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
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 분리
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로 측정·변경 분리
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 범위 격리
.card {
contain: layout style paint; /* 내부 변화가 외부 reflow 안 일으킴 */
}
.list-item {
contain: layout;
content-visibility: auto; /* 보이지 않으면 layout 스킵 */
}
Performance 측정 — DevTools Performance API
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 회피
// ❌ 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)
🤖 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 추가 |