브라우저 렌더링 원리: Reflow와 Repaint를 이해해야 최적화가 보인다.

크리스가 야심 차게 만든 애니메이션 메뉴가 왠지 모르게 버벅거린다.
"분명 최신 맥북인데 왜 뚝뚝 끊기지?"
코드를 열어보니 메뉴를 열기 위해 자바스크립트로 width 값을 1px씩 늘리고 있었다. 크리스는 방금 브라우저에게 엄청난 과로를 시킨 셈이다.
리액트의 가상 DOM(Virtual DOM)이 아무리 효율적이라 해도, 결국 브라우저가 화면을 그리는 원리를 모르면 성능 최적화는 반쪽짜리가 된다. 오늘은 브라우저가 픽셀을 화면에 찍어내는 과정, 중요 렌더링 경로(Critical Rendering Path)를 파헤쳐 본다.
브라우저의 공장 라인: 렌더링 파이프라인
브라우저가 HTML 파일을 받아서 화면에 띄우기까지는 일련의 조립 라인을 거친다.
성능 최적화의 핵심은 4번(Layout)과 5번(Paint) 과정을 얼마나 줄이느냐에 달려 있다.
Reflow와 Repaint: 성능을 잡아먹는 주범
1. Reflow (Layout): "다시 계산해!"
가장 비용이 비싼 작업이다. 요소의 **기하학적인 정보(너비, 높이, 위치)**가 변경되면, 브라우저는 해당 요소뿐만 아니라 영향을 받는 자식, 부모, 심지어 문서 전체의 배치를 다시 계산해야 한다.
마치 거실에 있는 소파 위치를 1cm 옮기기 위해, 나머지 가구들의 위치까지 다시 자로 재는 것과 같다.
Reflow를 유발하는 속성들:
2. Repaint (Paint): "색칠만 다시 해!"
Reflow보다는 저렴하지만 여전히 비용이 든다. 레이아웃(위치, 크기)은 변하지 않고, 눈에 보이는 스타일만 바뀔 때 발생한다.
Repaint를 유발하는 속성들:
중요: Reflow가 발생하면 무조건 Repaint도 함께 발생한다. 위치가 바뀌면 당연히 다시 그려야 하기 때문이다.
최적화 전략: GPU를 활용하라 (Composite)
그렇다면 애니메이션을 부드럽게(60fps) 만들려면 어떻게 해야 할까?
정답은 Layout과 Paint 단계를 아예 건너뛰는 것이다.
브라우저에는 합성(Composite) 단계만 거치는 속성들이 있다. 이들은 CPU가 아닌 GPU가 처리하여 매우 빠르다.
❌ 나쁜 예시: CPU 괴롭히기
크리스가 작성했던 코드다. left 속성을 변경하면 매 프레임마다 Reflow가 발생한다.
/* style.css */
.box {
position: absolute;
left: 10px;
transition: left 0.5s;
}
.box.move {
/* Layout(Reflow) 발생! -> Paint 발생! -> 느림 */
left: 100px;
}✅ 좋은 예시: GPU에게 떠넘기기
transform과 opacity는 레이아웃과 페인트 단계를 건너뛰고, 바로 합성(Composite) 단계로 넘어간다.
/* style.css */
.box {
/* GPU 가속을 위해 별도의 레이어로 분리됨 */
transform: translateX(10px);
transition: transform 0.5s;
}
.box.move {
/* Composite만 발생! -> 빠름 */
transform: translateX(100px);
}강제 동기 레이아웃 (Forced Synchronous Layout)
자바스크립트 코드 한 줄이 성능을 망칠 수도 있다. 브라우저는 원래 렌더링을 최적화하기 위해 변경 사항을 모아서 한 번에 처리(Batch)한다. 하지만 우리가 특정 값을 읽으려고 하면, 브라우저는 정확한 값을 주기 위해 큐를 비우고 즉시 레이아웃을 계산해버린다.
// layout-thrashing.js
const box = document.getElementById('box');
// 1. 스타일 쓰기 (Write)
box.style.width = '100px';
// 2. 스타일 읽기 (Read) -> 💥 강제 레이아웃 발생!
// 브라우저: "잠깐, 너가 width를 바꿨으니까 정확한 clientWidth를 알려주려면 지금 당장 계산(Reflow)해야 해."
console.log(box.clientWidth);
// 3. 다시 쓰기
box.style.height = '100px';이를 방지하려면 읽기(Read)와 쓰기(Write)를 섞어 쓰지 말고, 읽기를 먼저 다 한 뒤에 쓰기를 하거나 requestAnimationFrame을 활용해야 한다.
핵심 정리
브라우저가 그림을 그리는 법을 이해했으니, 이제 네트워크 비용을 줄이는 법을 알아볼 차례다. 사용자가 한 번 받은 이미지를 또 받지 않게 하려면 어떻게 해야 할까?
"HTTP 캐싱 완벽 가이드: Cache-Control, ETag로 네트워크 비용 줄이기" 편에서 계속된다.