코드 한 줄이 부르는 대참사: 리플로우(Reflow)와 리페인트(Repaint) 완벽 가이드

 웹 사이트를 만들다 보면 기능은 완벽한데 왠지 모르게 화면이 버벅거리거나, 스크롤이 매끄럽지 않은 경험을 해보셨을 겁니다. 10년 차 개발자로서 신입 사원들의 코드를 리뷰할 때 가장 먼저 확인하는 것 중 하나가 바로 "브라우저를 얼마나 괴롭히고 있는가"입니다.

단순히 document.getElementById로 스타일을 바꾸는 행위가 브라우저 내부에서는 얼마나 거대한 연산 과정을 유도하는지, 그리고 이를 어떻게 스마트하게 피할 수 있는지 실무적인 관점에서 상세히 풀어보겠습니다.


브라우저가 화면을 다시 그리는 두 가지 방식

우리가 자바스크립트로 DOM을 조작하면 브라우저는 바뀐 내용을 반영하기 위해 다시 계산을 시작합니다. 이때 발생하는 현상이 바로 **리플로우(Reflow)**와 **리페인트(Repaint)**입니다. 이 둘의 차이를 명확히 아는 것이 최적화의 시작입니다.

1. 리플로우(Reflow): 레이아웃의 재구성

리플로우는 문서 내 요소들의 위치와 크기를 다시 계산하는 과정입니다. 가장 비용이 많이 드는 작업이죠. 예를 들어, 어떤 요소의 width가 바뀌면 그 옆에 있던 요소, 그 아래에 있던 요소들의 위치까지 연쇄적으로 영향을 받습니다. 마치 도미노처럼 페이지 전체의 레이아웃을 다시 짜야 하는 상황이 벌어지는 것입니다.

2. 리페인트(Repaint): 색칠 공부의 재시작

리플로우가 끝난 뒤, 혹은 레이아웃(위치/크기)은 변하지 않고 시각적 요소(배경색, 글자색 등)만 바뀌었을 때 발생합니다. 리플로우보다는 가볍지만, 여전히 브라우저의 자원을 소모하는 작업임에는 틀림없습니다.


실무에서 무심코 저지르는 성능 저하 사례

"그냥 스타일 하나 바꿨을 뿐인데 왜 느려지죠?"라고 묻는 분들을 위해, 실제 프로젝트에서 서비스 장애에 가까운 성능 저하를 일으켰던 사례를 소개합니다.

루프 안에서의 스타일 수정 (강제 동기 레이아웃)

가장 흔한 실수입니다. 수백 개의 리스트 아이템의 너비를 자바스크립트 반복문 안에서 하나씩 변경하며, 동시에 그 너비값을 읽어오는 코드를 짠다고 가정해 봅시다.

JavaScript
// 나쁜 예시: 반복문 안에서 레이아웃 정보를 읽고 쓰기
for (let i = 0; i < items.length; i++) {
  const width = items[i].offsetWidth; // 읽기 (Layout 발생 가능)
  items[i].style.width = (width + 10) + 'px'; // 쓰기 (Reflow 유발)
}

브라우저는 정확한 offsetWidth 값을 알려주기 위해, 방금 바뀐 스타일을 적용한 최신 레이아웃을 계산해야 합니다. 즉, 반복문이 돌 때마다 매번 리플로우가 발생하는 '강제 동기 레이아웃' 지옥에 빠지게 됩니다. 이는 CPU 점유율을 순식간에 100%로 끌어올리는 주범입니다.


프로 개발자가 리플로우를 피하는 3가지 필살기

성능 최적화의 핵심은 **"브라우저가 몰아서 일하게 만드는 것"**입니다.

1. 스타일 변경은 한 번에 묶어서(Batching)

여러 개의 스타일을 하나씩 수정하지 마세요. 대신 CSS 클래스를 미리 정의해두고 classList를 통해 한 번에 교체하거나, cssText를 사용하여 한 번의 리플로우로 끝내야 합니다.

  • Bad: el.style.width = '10px'; el.style.height = '10px'; el.style.margin = '5px';

  • Good: el.className += ' active-style';

2. 레이아웃 정보 읽기는 변수에 담아서

offsetTop, getBoundingClientRect() 같은 속성들은 읽는 것만으로도 브라우저에게 레이아웃 계산을 강요할 수 있습니다. 이런 값들은 반복문 밖에서 변수에 한 번만 저장해두고 재사용하는 것이 원칙입니다.

3. 'display: none'의 전략적 활용

요소에 복잡한 수정을 가해야 한다면, 잠시 display: none으로 화면에서 숨긴 뒤 작업을 마치고 다시 보이게 하세요. display: none 상태인 요소는 렌더 트리에서 제외되므로, 내부에서 수백 번의 수정이 일어나도 리플로우가 단 한 번만 발생합니다.


0.1%의 부드러움을 결정하는 하드웨어 가속(GPU)

만약 애니메이션을 구현해야 한다면, 무조건 transformopacity 속성을 사용하십시오. 이 속성들은 일반적인 리플로우/리페인트 단계를 건너뛰고 '합성(Composite)' 단계로 바로 넘어갑니다.

브라우저는 이 요소들을 별도의 레이어(Layer)로 분리하여 GPU(그래픽 카드)에게 처리를 맡깁니다. CPU가 레이아웃 계산으로 바쁠 때, GPU가 독립적으로 화면을 그리기 때문에 프레임 드랍 없는 60FPS의 부드러운 화면을 제공할 수 있습니다.


글을 마치며: 사용자 경험이 곧 수익입니다

구글 애드센스 승인을 위해 고품질 포스팅을 작성하는 것도 중요하지만, 실제 승인 후 광고가 달렸을 때 페이지 로딩이 느려지면 사용자는 금방 떠나버립니다. 광고 스크립트 자체가 무겁기 때문에, 우리가 작성하는 기본 코드는 최대한 가벼워야 합니다.

오늘 배운 리플로우와 리페인트 관리 기법을 여러분의 블로그나 프로젝트에 적용해 보세요. "이 사이트는 정말 빠르고 쾌적하다"라는 느낌을 주는 순간, 검색 엔진의 평가(SEO)는 물론 사용자들의 체류 시간도 자연스럽게 늘어날 것입니다.

실무에서 성능 문제로 골머리를 앓고 계신가요? 혹은 특정 프레임워크에서의 최적화 방법이 궁금하신가요? 댓글로 상황을 공유해 주시면 함께 고민해 드리겠습니다.

댓글

이 블로그의 인기 게시물

밑빠진 독에 트래픽 붓기? GA4 데이터 분석으로 이탈률 잡고 수익 2배 펌핑하는 실무 로직