요소의 크기나 위치가 바뀌어 브라우저가 레이아웃을 다시 계산하는 일. 렌더링 파이프라인에서 대개 가장 비싼 단계이고, 리플로우가 일어나면 뒤따라 페인트와 합성도 다시 한다.
언제 일어나나
- DOM 노드를 추가·삭제·이동할 때,
display를none과 다른 값 사이에서 바꿀 때 - 기하에 영향을 주는 속성을 바꿀 때:
width·height,margin·padding·border두께,top·left,font-size·line-height, flex·grid 관련 속성 등 - 창 크기가 바뀔 때, 웹폰트나 이미지가 늦게 로드되어 크기가 정해질 때(이미지 크기를 비워 두면 CLS(Cumulative Layout Shift)도 나빠진다)
- 레이아웃 값을 읽을 때(
offsetWidth, getBoundingClientRect 등). 미뤄 둔 변경을 즉시 반영해야 해서 강제로 일어난다 → 레이아웃 스래싱
바뀐 요소 하나로 끝나지 않고 부모·형제·자식까지 번질 수 있다. 최악이면 문서 전체를 다시 계산한다.
숨기는 방법에 따라 비용이 다르다
| 방법 | 레이아웃 자리 | 바꿀 때 |
|---|---|---|
display: none | 사라진다(트리에서 빠짐) | 주변 배치가 바뀌어 리플로우 |
visibility: hidden | 그대로 남는다 | 기하가 그대로라 리페인트만 |
opacity: 0 | 그대로 남는다 | 합성 단계에서 처리할 수 있다 |
visibility: hidden과 opacity: 0은 보이지 않을 뿐 자리를 차지하고, opacity: 0인 요소는 여전히 클릭된다. 자주 켜고 끄는 요소는 자리를 비워 둬도 되는지에 따라 고른다.
줄이는 법
- 스타일을 하나씩 바꾸지 말고 클래스를 바꿔 한 번에 적용한다
- 노드를 여러 개 넣을 때는
DocumentFragment에 모아 한 번에 붙인다 - 레이아웃 값은 반복문 밖에서 한 번 읽어 변수에 담는다
- 움직이는 요소는
top·left대신transform을 쓴다 → 합성 단계와 transform·opacity - 크기가 바뀌는 요소를
position: absolute·fixed로 문서 흐름에서 빼 두면, 그 요소가 바뀌어도 형제 요소의 배치는 다시 계산하지 않아도 된다 - 불필요하게 깊은 DOM을 줄인다. 한 단계의 변경이 위로는 루트까지, 아래로는 바뀐 노드의 자식까지 번질 수 있다
- 화면 밖 영역은 content-visibility로 레이아웃 자체를 건너뛰게 할 수 있다
// 나쁨: 읽기마다 강제 리플로우
for (const el of items) el.style.height = box.offsetHeight + 10 + 'px'
// 좋음: 한 번 읽고 쓰기만 반복
const h = box.offsetHeight
for (const el of items) el.style.height = h + 10 + 'px'출처: MDN — visibility · MDN — display: none · web.dev — Avoid large, complex layouts and layout thrashing · Google — Minimizing browser reflow