Reflow와 Repaint 완벽 정리: 느린 웹의 진짜 원인
Reflow vs Repaint — 브라우저 렌더링 성능에 진짜 영향을 주는 요소들

최근에 렌더링 관련 성능을 점검하다가, Reflow와 Repaint의 차이를 다시 정리하게 됐어요.
이 둘은 웹 성능 이슈에서 정말 자주 등장하지만, 막상 정확히 구분해서 설명하는 글은 많지 않더라고요.
React, RealGrid, 또는 일반 DOM 조작을 하다 보면 무심코 발생하는 렌더링 비용이 바로 이 두 녀석입니다.
💡 Reflow와 Repaint란?
브라우저는 화면을 그릴 때 단순히 HTML만 해석하지 않아요.
DOM → CSSOM → Render Tree를 만들고, 그걸 기반으로 Layout(배치)과 Paint(그리기)를 수행합니다.
이 과정에서 변경이 생기면, 브라우저는 다시 계산을 해야 하죠.
| 용어 | 설명 |
|---|---|
| Reflow (Layout) | 요소의 크기나 위치가 바뀔 때 발생하는 레이아웃 재계산 |
| Repaint | 색상, 배경 등 시각적 스타일만 바뀔 때 발생하는 다시 그리기 |
한마디로, Reflow는 구조가 바뀔 때, Repaint는 색이 바뀔 때 생기는 작업이에요.
🧩 예를 들어보면
<div id="box" style="width:100px;height:100px;background:red;"></div>
<script>
const box = document.getElementById('box')
// 1️⃣ Reflow 발생
box.style.width = '200px' // 크기나 위치 변경 → 레이아웃 재계산
// 2️⃣ Repaint 발생
box.style.backgroundColor = 'blue' // 색상 변경 → 다시 그리기만 수행
</script>위 코드에서 width를 바꾸면 Reflow → Repaint가 모두 발생합니다.
하지만 backgroundColor만 바꾸면 Repaint만 일어나죠.
Reflow는 Paint보다 훨씬 비쌉니다.
브라우저는 바뀐 요소뿐 아니라, 그 영향을 받는 모든 하위·상위 요소의 배치까지 다시 계산해야 하거든요.
⚙️ 성능에 미치는 영향
Reflow는 생각보다 쉽게 발생합니다.
아래는 Reflow를 일으키는 대표적인 상황들이에요.
| 원인 | 설명 |
|---|---|
| DOM 크기 변경 | width, height, margin, padding, font-size 등 |
| DOM 구조 변경 | 노드 추가/삭제 (appendChild, removeChild) |
| 위치 변경 | top, left, position, display 등 |
| 윈도우 크기 변경 | resize 이벤트 |
| 스크롤 / overflow 변경 | 스크롤 발생 시 레이아웃 재배치 |
| offset, client, scroll 속성 접근 | 브라우저가 최신 레이아웃을 강제로 계산 |
이런 변경이 많아지면, 브라우저는 계속해서 Layout → Paint → Composite 단계를 반복해야 합니다.
🔬 실무에서 자주 겪는 예시
1️⃣ 반복 DOM 조작
// ❌ 비효율적인 방식
for (let i = 0; i < 1000; i++) {
const el = document.createElement('div')
el.textContent = i
document.body.appendChild(el) // 매번 Reflow 발생
}이런 코드는 1000번의 Reflow를 유발합니다.
브라우저는 append될 때마다 레이아웃을 다시 계산하니까요.
✅ 해결 방법: DocumentFragment를 이용해서 한 번에 추가
const frag = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
const el = document.createElement('div')
el.textContent = i
frag.appendChild(el)
}
document.body.appendChild(frag) // 단 한 번의 Reflow2️⃣ 스타일 속성 연속 변경
// ❌ Reflow 여러 번 발생
box.style.width = '200px'
box.style.height = '200px'
box.style.margin = '20px'✅ 해결 방법: classList 또는 CSS text로 묶기
box.classList.add('expanded')
// 혹은
box.style.cssText = 'width:200px;height:200px;margin:20px;'이렇게 하면 브라우저가 한 번에 렌더링을 갱신할 수 있습니다.
3️⃣ 강제 Reflow (Forced Reflow)
이건 진짜 조심해야 할 부분이에요.
아래처럼 레이아웃 정보를 읽는 코드와 수정 코드가 섞여 있으면 브라우저가 강제로 Reflow를 실행합니다.
el.style.width = '200px'
console.log(el.offsetWidth) // 💥 강제 Reflow 발생왜냐면 offsetWidth를 읽을 때 브라우저는 “최신 레이아웃”을 보장해야 하니까
모든 계산을 다시 수행하게 됩니다.
즉, “읽기-쓰기-읽기” 패턴은 성능의 적입니다.
🧠 실무에서의 최적화 팁
| 상황 | 권장 방식 |
|---|---|
| 여러 요소를 수정해야 할 때 | DocumentFragment / display:none 처리 후 일괄 변경 |
| 스타일 변경이 많은 경우 | CSS 클래스로 묶어서 교체 |
| scroll, resize 이벤트 | Debounce / Throttle로 제어 |
| 애니메이션 | transform, opacity 속성 중심으로 (Reflow 없음) |
| Reflow 강제 발생 방지 | offset, scroll 속성 접근 최소화 |
Reflow는 줄일 수는 있어도 완전히 없앨 수는 없어요.
하지만 “언제, 왜 발생하는지” 알고 있으면 대부분의 성능 문제를 예방할 수 있습니다.
⚡ Reflow를 피하고 싶은 CSS 팁
transform과opacity는 Reflow 없이 애니메이션 가능position: absolute또는fixed를 이용해 영향 범위 축소will-change로 브라우저에게 미리 최적화 힌트 제공contain속성으로 하위 요소의 레이아웃 독립성 보장 (CSS Containment)
이런 속성들은 Chrome DevTools의 Rendering 탭 → “Paint flashing” 옵션으로 확인할 수 있어요.
📊 정리
| 구분 | Reflow | Repaint |
|---|---|---|
| 발생 시점 | 레이아웃 구조 변경 시 | 시각적 속성 변경 시 |
| 비용 | 높음 | 중간 |
| 대표 트리거 | width, height, position, font-size 등 | color, background 등 |
| 최적화 포인트 | 레이아웃 변경 최소화 | 불필요한 페인트 줄이기 |
💬 마무리하며
Reflow와 Repaint는 눈에 보이지 않지만, 브라우저 성능에 직접적인 영향을 주는 요소예요.
특히 React나 Vue처럼 Virtual DOM을 쓰더라도, 결국 마지막 단계에서 Real DOM 업데이트가 일어나면 Reflow는 피할 수 없습니다.
즉, Virtual DOM은 “Reflow를 덜 일으키는 전략”일 뿐, 없애주는 기술이 아니에요.
이 차이를 이해하고 코드를 작성하면, 훨씬 부드럽고 효율적인 UI를 만들 수 있습니다.
브라우저는 생각보다 똑똑하지만, 작은 실수 하나에도 큰 비용을 치릅니다.
코드를 짤 때 항상 “지금 이 줄이 Reflow를 유발할까?”를 한번쯤 떠올려보세요.
#Reflow #Repaint #브라우저렌더링 #렌더링성능 #CSS성능 #JavaScript성능 #웹최적화 #프론트엔드성능 #Reflow최적화 #VirtualDOM