--- id: wiki-2026-0508-성능-최적화-reflow-repaint title: "성능 최적화(Reflow & Repaint)" category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Reflow, Repaint, Layout Thrash, 레이아웃 트래싱] duplicate_of: none source_trust_level: A confidence_score: 0.93 verification_status: applied tags: [frontend, performance, browser, rendering, dom] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: javascript framework: browser --- # 성능 최적화(Reflow & Repaint) ## 매 한 줄 > **"매 layout(=reflow) 의 비싸고, paint 의 cheap-er, composite 의 cheapest"**. 매 DOM/CSS write 의 invalidate 의 trigger — geometry change → reflow + repaint, color-only change → repaint, transform/opacity → composite-only. Layout thrashing 의 batch read/write 의 fix. ## 매 핵심 ### 매 cost ladder 1. **Composite only** (cheap, 60fps): `transform`, `opacity`, `filter` (some). 2. **Paint** (medium): `color`, `background-color`, `box-shadow`, `border-radius`. 3. **Layout (Reflow)** (expensive): `width`, `height`, `top/left`, `margin`, `padding`, `font-size`, `display`. 4. **Full reflow trigger**: `` font change, viewport resize. ### 매 reflow triggers (write) - DOM 의 add/remove. - `display`/`position` change. - size/box-model property change. - font/text content change. - pseudo-class (`:hover`) 의 layout-affecting style. ### 매 forced sync layout (read) - `offsetTop/Left/Width/Height`, `clientTop/...`, `scrollTop/...`. - `getComputedStyle()`, `getBoundingClientRect()`. - 매 pending invalidation 의 있을 때 read → 매 browser 의 immediate layout 의 trigger. ## 💻 패턴 ### Layout thrash 의 fix (read-then-write) ```js // X — N reflow (each iteration forces layout) boxes.forEach(b => { const w = b.offsetWidth; b.style.width = (w + 10) + 'px'; }); // O — 1 reflow (read all, then write all) const widths = boxes.map(b => b.offsetWidth); boxes.forEach((b, i) => b.style.width = (widths[i] + 10) + 'px'); ``` ### `requestAnimationFrame` batching ```js let pending = false; function update(el) { if (pending) return; pending = true; requestAnimationFrame(() => { el.style.transform = `translateX(${nextX}px)`; pending = false; }); } window.addEventListener('scroll', () => update(stickyEl)); ``` ### Detach → mutate → reattach (huge DOM ops) ```js const list = document.getElementById('list'); const fragment = document.createDocumentFragment(); for (const item of items) { const li = document.createElement('li'); li.textContent = item.name; fragment.appendChild(li); } list.appendChild(fragment); // 1 reflow, not N ``` ### `transform` 의 substitute (animation) ```css /* X — top change → reflow per frame */ @keyframes slide-bad { from { top: 0 } to { top: 100px } } /* O — transform → composite only */ @keyframes slide-good { from { transform: translateY(0) } to { transform: translateY(100px) } } ``` ### `will-change` (sparingly) ```css /* 매 animation 직전 의 hint, 매 finish 후 remove */ .card { will-change: transform; } .card.done { will-change: auto; } ``` ### `contain` (CSS containment) ```css /* 매 subtree 의 layout/paint 의 isolate — outer 의 invalidation 의 not propagate */ .widget { contain: layout paint; } ``` ### Position fixed/sticky (composite layer) ```css .toolbar { position: sticky; top: 0; /* GPU layer — scroll 시 reflow 없음 */ } ``` ### IntersectionObserver (no scroll handler) ```js const io = new IntersectionObserver((entries) => { for (const e of entries) { if (e.isIntersecting) e.target.classList.add('in-view'); } }, { rootMargin: '0px 0px -10% 0px' }); images.forEach(img => io.observe(img)); ``` ## 매 결정 기준 | 변경 | Cost | |---|---| | `transform` / `opacity` | composite only — 60fps OK | | `background-color` | paint — usually OK | | `top`/`left`/`width`/`height` | layout — animation 의 avoid | | `display` | layout — modal toggle 의 OK, animation 의 X | | `font-size` (root) | full document reflow — extreme cost | **기본값**: 매 animation → `transform/opacity` 만, batch read/write, large insert → DocumentFragment, off-screen → `content-visibility` / IntersectionObserver. ## 🔗 Graph - 부모: [[브라우저 렌더링 파이프라인(Critical Rendering Path)]] - 변형: [[Compositor Thread]] · [[GPU Layer Promotion]] - 응용: [[Virtual List]] · [[Animation Performance]] - Adjacent: [[`will-change`]] · [[CSS Containment]] · [[content-visibility]] ## 🤖 LLM 활용 **언제**: layout thrash 의 detect (read-write interleave pattern), animation property 의 review. **언제 X**: actual paint profiling — Chrome DevTools Performance/Layers panel 의 use. ## ❌ 안티패턴 - **`will-change` everywhere**: 매 GPU memory exhaustion. - **`top/left` animation**: 매 reflow per frame — `transform` 의 substitute. - **scroll handler 의 layout read**: 매 thrash — IntersectionObserver 의 use. - **`innerHTML` 의 loop**: 매 N reflow — fragment 의 build. ## 🧪 검증 / 중복 - Verified (web.dev/articles/animations-guide, Paul Lewis "Avoid Large, Complex Layouts", CSSOM View spec). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — reflow trigger + 8 패턴 + 결정 기준 의 정리 |