리플로우(Reflow)와 리페인트(Repaint) 완벽 비교 가이드: 브라우저 렌더링 최적화
프론트엔드 개발자로서 성능 최적화를 논할 때 가장 많이 언급되는 단어 중 하나가 바로 **리플로우(Reflow)**와 **리페인트(Repaint)**입니다. 특히 React와 같은 라이브러리를 사용하다 보면 프레임 드랍이나 미세한 렉을 마주하게 되는데, 대개 그 원인은 브라우저의 렌더링 파이프라인을 비효율적으로 사용했기 때문입니다. 오늘은 이 두 개념의 차이점과 실무에서 어떻게 최적화해야 하는지 상세히 정리해 보겠습니다.
목차
- 브라우저 렌더링 파이프라인의 이해
- 리플로우(Reflow)란 무엇인가?
- 리페인트(Repaint)란 무엇인가?
- 성능 최적화를 위한 실무 사례 및 팁
- 개발자의 팁: 하드웨어 가속 활용하기
브라우저 렌더링 파이프라인의 이해
브라우저가 HTML 문서를 받아 화면에 그리기까지는 여러 단계를 거칩니다. 이를 Critical Rendering Path (CRP)라고 부릅니다.
- DOM + CSSOM: HTML과 CSS를 파싱하여 트리 구조를 만듭니다.
- Render Tree: 위 두 트리를 결합하여 화면에 실제로 보일 노드들만 모은 트리를 생성합니다.
- Layout (Reflow): 각 노드가 화면 어디에, 어떤 크기로 위치할지 계산합니다.
- Paint (Repaint): 위치가 결정된 노드들에 색상을 입히고 그림자, 텍스트 등을 그립니다.
- Composite: 여러 레이어를 하나로 합쳐 최종 화면을 구성합니다.
리플로우(Reflow)란 무엇인가?
**리플로우(Reflow)**는 렌더링 파이프라인의 Layout 단계를 다시 수행하는 것을 의미합니다. 즉, 요소의 기하학적인 속성(너비, 높이, 위치, 폰트 크기 등)이 변경되어 브라우저가 전체 혹은 일부의 위치를 다시 계산해야 할 때 발생합니다.
- 사례: 브라우저 창 크기 조절(Resize), 요소 추가/삭제,
width,height,margin,padding,display,font-size변경 등. - 영향력: 리플로우는 주변 요소나 부모/자식 요소까지 연쇄적으로 계산을 불러일으키기 때문에 비용이 매우 높습니다.
리페인트(Repaint)란 무엇인가?
**리페인트(Repaint)**는 요소의 위치나 크기에는 변화가 없고, 오직 **시각적 외형(색상 등)**만 바뀔 때 발생합니다.
- 사례:
background-color,visibility,outline,color,box-shadow변경 등. - 영향력: 레이아웃 수치를 다시 계산하지 않으므로 리플로우보다는 가볍지만, 여전히 브라우저가 화면을 다시 그려야 하므로 빈번한 발생은 지양해야 합니다.
성능 최적화를 위한 실무 사례 및 팁
1. 인라인 스타일 변경 최소화
여러 개의 스타일을 하나씩 변경하면 그때마다 리플로우가 발생할 수 있습니다. 대신 클래스를 한 번에 교체하거나 cssText를 활용하세요.
// 비효율적: 리플로우 3번 발생 가능
const el = document.getElementById('box') as HTMLElement;
el.style.width = '100px';
el.style.height = '100px';
el.style.margin = '10px';
// 효율적: 리플로우 1번으로 제한
el.classList.add('is-active');
2. 레이아웃 스래싱(Layout Thrashing) 방지
스타일을 쓰기(Write) 전에 읽기(Read)를 반복하면 브라우저가 최신 값을 제공하기 위해 강제로 리플로우를 발생시킵니다.
// 나쁜 예: 반복문 안에서 레이아웃 정보를 읽고 쓰는 행위
items.forEach((item) => {
const width = container.offsetWidth; // Read (강제 리플로우 유발 가능)
item.style.width = `${width}px`; // Write
});
// 좋은 예: 읽기를 밖으로 빼서 한 번만 수행
const width = container.offsetWidth;
items.forEach((item) => {
item.style.width = `${width}px`;
});
개발자의 팁: 하드웨어 가속 활용하기
리플로우와 리페인트를 모두 피하고 싶다면 Composite 단계만 활용하는 것이 베스트입니다. CSS 속성 중 transform과 opacity는 브라우저의 GPU(하드웨어 가속)를 사용하여 레이아웃과 페인트 단계를 건너뛸 수 있게 해줍니다.
Tip: 애니메이션을 구현할 때
top/left를 조절하는 대신transform: translate()를 사용하세요. 전자는 리플로우를 일으키지만, 후자는 컴포지트 단계에서만 처리되어 60fps의 부드러운 움직임을 보장합니다.
자주 묻는 질문 (FAQ)
Q1. visibility: hidden과 display: none의 차이는 무엇인가요?
display: none은 레이아웃에서 아예 제거되므로 리플로우와 리페인트를 모두 발생시킵니다. 반면 visibility: hidden은 공간은 차지하되 보이지만 않게 하므로 리페인트만 발생시킵니다.
Q2. 리액트의 Virtual DOM은 리플로우를 어떻게 해결하나요?
Virtual DOM은 메모리상에서 변경 사항을 먼저 계산하고, 최종적으로 바뀐 부분만 실제 DOM에 배치 처리(Batching)하여 적용합니다. 이 과정에서 수많은 리플로우 발생을 최소한의 횟수로 묶어주는 역할을 합니다.
Q3. GPU 가속을 위해 모든 요소에 translateZ(0)를 넣어도 될까요?
아니요. 과도한 레이어 생성은 메모리 사용량을 급증시켜 오히려 성능 저하를 초래할 수 있습니다. 꼭 필요한 애니메이션 요소에만 선택적으로 사용해야 합니다.
렌더링 파이프라인의 원리를 이해하면 코드 한 줄이 브라우저에 주는 부담을 가늠할 수 있습니다. 다음 프로젝트에서는 transform과 will-change를 적절히 활용하여 더 부드러운 사용자 경험을 만들어 보세요!