Frontend

[Frontend] Reflow는 왜 느릴까?

teddy bear 2026. 7. 3. 16:32
반응형
Reflow는 왜 느릴까
FRONTEND · BROWSER RENDERING

Reflow는 왜 느릴까

"레이아웃 계산이 무겁다"는 말은 자주 듣지만, 정확히 무엇이 느린 건지는 은근히 설명하기 어려워요. 렌더링 파이프라인을 따라가면서 Reflow가 왜 다른 단계보다 비싼지, 그리고 왜 코드 한 줄이 수십 개의 요소를 다시 계산하게 만드는지 짚어볼게요.

렌더링 파이프라인 먼저

브라우저는 DOM이 바뀔 때마다 5단계를 거쳐 화면을 다시 그려요. 이 중 어디서부터 다시 실행하느냐가 성능을 가릅니다.

DOM 변경 요소 추가·삭제·수정 스타일 재계산 적용할 CSS 규칙 결정 레이아웃 (Reflow) 전체 기하 정보 재계산 · 가장 비쌈 페인트 픽셀 색상 채우기 합성 레이어 합쳐 화면 표시 파란색 = 상대적으로 저렴 · 빨간색 = 가장 비싼 단계

transform이나 opacity만 바꾸면 스타일 재계산과 페인트까지는 건드리지 않고 합성 단계에서 바로 처리돼요. 반대로 width, top, font-size처럼 기하 정보를 바꾸는 속성은 반드시 레이아웃부터 다시 타고 내려와야 해요. 문제는 이 레이아웃 단계가 유독 무겁다는 거예요.

왜 하필 레이아웃이 비쌀까

핵심은 이거예요. 레이아웃은 요소 하나만 독립적으로 계산할 수 없는 문제예요.

페인트는 "이 픽셀에 이 색을 칠해라"처럼 지역적인 작업이지만, 레이아웃은 "이 요소가 화면 어디에, 얼마나 크게 있어야 하는가"를 계산해요. 그런데 한 요소의 크기와 위치는 부모의 너비, 형제 요소들이 차지한 공간, 자식 콘텐츠의 양에 따라 달라져요. 즉 기하 정보는 트리 전체에 걸쳐 서로 얽혀있는 제약 충족 문제에 가까워요.

부모 컨테이너 자식이 순서대로 쌓임 요소 1 요소 2 텍스트가 늘어나 높이 증가 (+60px) 요소 3 요소 4 점선 = 원래 위치 실선 = 밀려난 위치 여기서 높이가 바뀜

요소 2 하나의 높이가 늘어났을 뿐인데, 그 아래 있는 요소 3, 4는 전부 새로운 위치를 다시 계산해야 해요. 자식이 많고 트리가 깊을수록 이 파급 효과는 더 넓게 퍼지고, 최악의 경우 문서 전체를 다시 계산해야 하는 상황(전역 reflow)까지 이어져요.

Layout Thrashing — 스스로 발목 잡기

진짜 성능 문제는 대부분 여기서 시작해요. 브라우저는 원래 여러 개의 DOM 변경을 모아뒀다가 한 번에 레이아웃을 계산하려고 해요(배치 처리). 그런데 자바스크립트가 DOM을 쓴 직후에 곧바로 크기·위치 값을 읽으면, 브라우저는 정확한 값을 주기 위해 예정에 없던 레이아웃을 즉시 강제로 실행해요. 이걸 forced synchronous layout, 반복되면 layout thrashing이라고 불러요.

bad — 읽기·쓰기가 번갈아 일어남
// 매 반복마다 강제 레이아웃 발생
boxes.forEach(box => {
  const h = box.offsetHeight        // 읽기 → 레이아웃 강제 실행
  box.style.height = (h + 10) + 'px'  // 쓰기 → 다음 읽기 전까지 대기중
})
good — 읽기와 쓰기를 분리(batch)
// 모든 값을 먼저 읽고, 그 다음에 전부 쓴다
const heights = boxes.map(box => box.offsetHeight)
boxes.forEach((box, i) => {
  box.style.height = (heights[i] + 10) + 'px'
})

읽기를 전부 끝낸 다음 쓰기를 모아서 실행하면, 브라우저는 레이아웃을 딱 한 번만 계산하면 돼요. 100개 요소를 반복문 안에서 읽고 쓰면 최악의 경우 100번의 강제 레이아웃이 발생할 수 있어요.

강제 레이아웃을 유발하는 대표적인 속성

offsetTop/Left/Width/Height, clientWidth/Height, scrollTop/Height, getComputedStyle(), getBoundingClientRect() — 이런 값을 DOM 쓰기 직후에 읽으면 브라우저가 대기 중이던 변경 사항을 즉시 반영해서 값을 계산해줘요.

속성별 비용 — 뭘 바꾸느냐가 다르다

단계대표 속성비용
Layout부터width, height, top, left, font-size, padding비쌈
Paint부터color, background, box-shadow, border-radius중간
Composite만transform, opacity저렴

그래서 애니메이션을 만들 때는 left를 바꾸는 대신 transform: translateX()를 쓰라는 말이 나오는 거예요. 결과는 비슷해 보여도, 하나는 매 프레임마다 레이아웃을 다시 계산하고 다른 하나는 GPU가 레이어만 이동시켜요.

정리하면

  • 레이아웃은 요소 하나만의 문제가 아니라 부모·형제·자식이 얽힌 전역 계산이라 비싸다
  • 한 요소의 크기 변화는 그 아래 형제들의 위치까지 연쇄적으로 다시 계산하게 만든다
  • DOM 쓰기 직후 크기·위치를 읽으면 예정된 배치 처리를 깨고 강제 레이아웃을 유발한다
  • 애니메이션은 가능하면 transform·opacity로, 읽기와 쓰기는 반드시 분리해서 처리한다

Browser Rendering Series · Reflow