[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -2,99 +2,161 @@
|
||||
id: wiki-2026-0508-성능-최적화-reflow-repaint
|
||||
title: "성능 최적화(Reflow & Repaint)"
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: []
|
||||
aliases: [Reflow, Repaint, Layout Thrash, 레이아웃 트래싱]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [uncategorized]
|
||||
confidence_score: 0.93
|
||||
verification_status: applied
|
||||
tags: [frontend, performance, browser, rendering, dom]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-08
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
language: javascript
|
||||
framework: browser
|
||||
---
|
||||
|
||||
# [[성능 최적화([[Reflow & Repaint]])]]
|
||||
# 성능 최적화(Reflow & Repaint)
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
Reflow(리플로우)는 요소의 크기나 위치 등 레이아웃이 변경될 때 브라우저가 문서의 구조와 기하학적 형태를 다시 계산하는 과정이며, Repaint(리페인트)는 레이아웃에 영향을 주지 않는 시각적 요소(색상, 배경 등)가 변경될 때 화면에 다시 픽셀을 그리는 과정입니다. 이 두 과정은 브라우저의 렌더링 리소스를 크게 소모하므로 성능 저하와 프레임 드롭의 주요 원인이 됩니다. 유지보수성이 높고 사용자 경험(UX)이 뛰어난 CSS를 설계하기 위해서는 불필요한 리플로우와 리페인트를 최소화하는 렌더링 최적화 전략이 필수적입니다.
|
||||
## 매 한 줄
|
||||
> **"매 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.
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
**Reflow와 Repaint의 개념 및 발생 원인**
|
||||
* **Reflow (Layout)**: 브라우저가 렌더링 과정에서 페이지의 레이아웃을 다시 계산하는 단계입니다. `width`, `height`, `margin`, `padding`, `top`, `left`와 같이 요소의 기하학적 구조를 변경하는 속성이 애니메이션되거나 조작될 때 발생합니다 [1, 2]. 한 요소에서 리플로우가 발생하면 자식 요소, 부모 요소 및 DOM 트리 구조에서 뒤따르는 다른 요소들까지 연쇄적인 리플로우가 유발되므로 성능에 매우 치명적입니다 [3]. 리플로우는 창 크기 조절, 폰트 변경, DOM 노드 조작, 그리고 [[JavaScript]]에서 `offsetWidth`나 `offsetHeight`를 계산하여 레이아웃 정보를 읽어올 때도 유발됩니다 [4].
|
||||
* **Repaint (Paint)**: 레이아웃이나 요소의 기하학적 구조에는 영향을 주지 않으면서 `color`, `background-color`, `visibility`, `box-shadow`, `outline` 같은 시각적 속성만 변경될 때 발생합니다 [2, 3, 5]. 리플로우보다는 상대적으로 가볍지만, 브라우저가 다른 노드들의 가시성 등을 확인하고 다시 그려야 하므로 여전히 높은 연산 비용이 듭니다 [3, 6].
|
||||
## 매 핵심
|
||||
|
||||
**성능 최적화(Reflow & Repaint 감소) 핵심 기법**
|
||||
* **Compositing(합성) 속성 활용**: 애니메이션을 구현할 때는 리플로우와 리페인트를 유발하는 속성 대신 `transform`과 `opacity`를 사용하는 것이 가장 이상적입니다 [6, 7]. 이 속성들은 브라우저의 GPU 가속을 통해 처리(Composite 단계만 수행)되므로 레이아웃 재계산이나 페인팅 과정 없이 부드러운 60fps 애니메이션을 제공합니다 [2, 8].
|
||||
* **DOM 조작 및 스타일 변경의 일괄 처리([[Batching]])**: JavaScript를 통해 개별적으로 여러 개의 인라인 스타일을 설정하면 잦은 리플로우가 발생합니다 [9]. 이를 방지하려면 `documentFragment`를 활용해 DOM 조작을 일괄 처리하거나, JavaScript로 개별 속성을 변경하는 대신 CSS 클래스를 한 번에 교체하는 방식을 사용해야 합니다 [10, 11].
|
||||
* **[[Layout Thrashing]](레이아웃 스래싱) 방지**: JavaScript 루프 내부에서 DOM의 레이아웃 값(예: `offsetWidth`)을 읽고 즉시 쓰는 작업을 반복하면, 브라우저가 정확한 값을 반환하기 위해 강제로 동기적인 리플로우를 수행하여 프레임률이 급감합니다 [12, 13]. DOM 읽기와 쓰기 작업을 분리하고, `requestAnimationFrame`을 사용하여 브라우저의 리페인트 주기에 맞춰 애니메이션을 동기화해야 합니다 [12, 13].
|
||||
* **DOM 트리 하단부에서의 클래스 변경**: 리플로우의 연쇄 작용 스코프를 제한하기 위해, 최상위 래퍼(Wrapper)나 부모 요소 대신 화면 변경에 직접적인 영향을 받는 가장 하위의 DOM 노드에서 클래스를 변경하는 것이 좋습니다 [14].
|
||||
* **레이아웃 흐름에서 분리 (`position: absolute/fixed`)**: 애니메이션이 적용되는 요소에 `position: absolute` 또는 `fixed`를 부여하면 해당 요소가 문서의 정상적인 흐름(Flow)에서 빠지게 됩니다 [9, 15]. 결과적으로 애니메이션 중 주변 요소들의 레이아웃에 영향을 주지 않아 전체 리플로우 대신 해당 요소의 리페인트만 유발하게 됩니다 [15].
|
||||
* **`will-change` 속성의 활용**: 특정 요소가 곧 애니메이션될 것임을 브라우저에 미리 알려주어 렌더링 최적화를 준비하게 할 수 있습니다 [16, 17]. 이 속성을 사용하면 브라우저가 요소를 최적화할 수 있지만, 성능 문제 발생을 방지하는 최후의 수단으로 써야 하며 과도한 사용은 오히려 성능을 저하시킬 수 있습니다 [16, 18].
|
||||
* **테이블 레이아웃 및 복잡한 선택자 회피**: 테이블(`<table>`)은 내용물이나 셀 하나의 크기가 변해도 전체 테이블 노드의 레이아웃을 다시 계산하기 위해 여러 번의 리플로우 패스를 거칠 수 있어 레이아웃 용도로 부적합합니다 [19]. 또한 불필요하게 복잡하거나 깊게 중첩된 CSS 선택자는 브라우저의 파싱 속도를 늦추므로 최대한 직관적이고 단순하게 작성해야 합니다 [11, 20].
|
||||
### 매 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**: `<html>` font change, viewport resize.
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
- **Related Topics:** CSS 애니메이션(transition / keyframes), CSS 아키텍처 및 렌더링 파이프라인
|
||||
- **Projects/Contexts:** [[실무에서 CSS 관리하는 방법]], 반응형 디자인 및 고성능 UI 구현
|
||||
- **Contradictions/Notes:** 소스 문헌들은 `will-change` 속성이 리플로우 및 페인트를 대비한 최적화에 도움을 준다고 소개하면서도, 이를 너무 많은 요소에 남용하거나 미리 예측하여 사용하면 도리어 브라우저 리소스와 메모리를 소모시켜 성능 병목(bottleneck)을 유발할 수 있다고 일관되게 경고합니다 [16-18].
|
||||
### 매 reflow triggers (write)
|
||||
- DOM 의 add/remove.
|
||||
- `display`/`position` change.
|
||||
- size/box-model property change.
|
||||
- font/text content change.
|
||||
- pseudo-class (`:hover`) 의 layout-affecting style.
|
||||
|
||||
---
|
||||
*Last updated: 2026-04-26*
|
||||
### 매 forced sync layout (read)
|
||||
- `offsetTop/Left/Width/Height`, `clientTop/...`, `scrollTop/...`.
|
||||
- `getComputedStyle()`, `getBoundingClientRect()`.
|
||||
- 매 pending invalidation 의 있을 때 read → 매 browser 의 immediate layout 의 trigger.
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
## 💻 패턴
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
### 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';
|
||||
});
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
|
||||
- **과거 데이터와의 충돌:** 없음
|
||||
- **정책 변화:** 없음
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
// 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');
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
### `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));
|
||||
```
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
### 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
|
||||
```
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
### `transform` 의 substitute (animation)
|
||||
```css
|
||||
/* X — top change → reflow per frame */
|
||||
@keyframes slide-bad { from { top: 0 } to { top: 100px } }
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
/* O — transform → composite only */
|
||||
@keyframes slide-good { from { transform: translateY(0) } to { transform: translateY(100px) } }
|
||||
```
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
### `will-change` (sparingly)
|
||||
```css
|
||||
/* 매 animation 직전 의 hint, 매 finish 후 remove */
|
||||
.card { will-change: transform; }
|
||||
.card.done { will-change: auto; }
|
||||
```
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
### `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 패턴 + 결정 기준 의 정리 |
|
||||
|
||||
Reference in New Issue
Block a user