--- id: wiki-2026-0508-레이아웃-스래싱-layout-thrashing title: 레이아웃 스래싱 (Layout Thrashing) category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Layout Thrashing, Forced Synchronous Layout, FSL] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [performance, rendering, javascript, frontend] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: javascript framework: browser --- # 레이아웃 스래싱 (Layout Thrashing) ## 매 한 줄 > **"매 layout thrashing = read DOM geometry → write style → read DOM 매 반복 — 매 force sync layout 매번"**. 매 브라우저 매 batch reflow 의 X — 매 즉시 reflow 강제 — 매 100x 느림. 매 해결 = 매 read 모두 batch → 매 write 모두 batch. ## 매 핵심 ### 매 트리거 (read = force layout) - `offsetTop/Left/Width/Height`. - `clientTop/Left/Width/Height`. - `scrollTop/Left/Width/Height`. - `getBoundingClientRect()`. - `getComputedStyle()` (some properties). - `innerText` (특정 case). ### 매 메커니즘 - 매 write (`el.style.x = ...`) — 매 layout invalidate, 매 brower 매 batch 대기. - 매 read (`el.offsetWidth`) — 매 latest geometry 필요 — 매 강제 reflow. - 매 loop 안 read/write alternate — 매 매번 reflow — 매 thrashing. ### 매 해결 1. **Batch**: read 모두 → write 모두. 2. **Cache**: 매 1회 read, 매 변수 저장. 3. **rAF**: read in current frame, write in next frame. 4. **FastDOM** library — read/write queue. 5. **CSS containment**: `contain: layout` — 매 thrashing 매 isolate. ## 💻 패턴 ### Anti — 매 thrashing ```javascript const items = document.querySelectorAll('.item'); items.forEach(el => { // 매 read → 매 write → 매 read → 매 write — 매 thrashing el.style.width = el.offsetWidth + 10 + 'px'; }); // 매 100 items → 매 100 layouts ``` ### Fix — batch ```javascript const items = document.querySelectorAll('.item'); // 매 phase 1: read 모두 const widths = Array.from(items).map(el => el.offsetWidth); // 매 phase 2: write 모두 items.forEach((el, i) => { el.style.width = widths[i] + 10 + 'px'; }); // 매 1 layout ``` ### rAF read/write split ```javascript function syncSizes() { // 매 current frame: read const heights = Array.from(rows).map(r => r.offsetHeight); requestAnimationFrame(() => { // 매 next frame: write rows.forEach((r, i) => { r.style.minHeight = heights[i] + 'px'; }); }); } ``` ### FastDOM ```javascript import fastdom from 'fastdom'; fastdom.measure(() => { const w = el.offsetWidth; fastdom.mutate(() => { el.style.width = w + 10 + 'px'; }); }); // 매 internally batch — 매 1 layout per frame ``` ### ResizeObserver (매 read 필요 X) ```javascript const ro = new ResizeObserver(entries => { for (const entry of entries) { const { width, height } = entry.contentRect; // 매 already computed entry.target.dataset.width = width; // 매 write — 매 layout 의 X } }); ro.observe(element); ``` ### CSS Containment (isolate) ```css .widget { contain: layout; /* 매 widget 내부 변경 — 매 outer 매 reflow 의 X */ } ``` ### DevTools 측정 ```javascript performance.mark('layout-start'); heavyOperation(); performance.mark('layout-end'); performance.measure('layout', 'layout-start', 'layout-end'); // 매 DevTools Performance panel — 매 purple "Layout" bar — 매 thrashing 표시 ``` ### Virtual list (대량 item) ```javascript // 매 모든 item layout — 매 X // 매 visible 만 render — 매 react-virtual / @tanstack/virtual import { useVirtualizer } from '@tanstack/react-virtual'; const rowVirtualizer = useVirtualizer({ count: 10000, getScrollElement: () => parentRef.current, estimateSize: () => 40, }); ``` ## 매 결정 기준 | 상황 | 접근 | |---|---| | Loop 안 size 읽고 변경 | batch read → batch write | | 비동기 read/write | rAF split | | Element resize 감지 | ResizeObserver | | Component-level isolation | `contain: layout` | | 1000+ rows | Virtualization | | Library 매 abstraction | FastDOM | **기본값**: read all → write all + 매 ResizeObserver (size 감지) + 매 Virtual list (대량). ## 🔗 Graph - 부모: [[리플로우 및 리페인트(Reflow and Repaint)]] · [[Web Performance]] - 변형: [[Forced Synchronous Layout]] - Adjacent: [[ResizeObserver]] ## 🤖 LLM 활용 **언제**: jank 디버깅, "왜 scroll 느림", DevTools Performance 분석. **언제 X**: 매 framework-specific (React reconciler) 내부 — 매 framework docs. ## ❌ 안티패턴 - **Loop 안 read+write**: 매 thrashing 의 정의. - **`offsetWidth` after style write**: 매 force layout. - **jQuery `.css()` chain**: 매 read/write 매 mix. - **`scrollTop` polling rAF**: 매 ScrollObserver/IntersectionObserver 의 사용. - **모든 row layout 매 손수**: 매 virtual list 의 사용. ## 🧪 검증 / 중복 - Verified (web.dev Avoid Layout Thrashing, Paul Irish gist, Chrome DevRel). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — read/write batch + rAF + ResizeObserver |