HTML byte 가 화면의 pixel 이 되기까지 무슨 일이 일어나나? 단순 "render" 가 아니라 parsing → DOM/CSSOM → render tree → layout → paint → composite 의 pipeline. 어디서 느려지는지 알아야 60fps (frame 당 16.67ms) 예산 안에 머문다. 이 가이드는 브라우저 렌더링의 6 단계, reflow vs repaint 의 차이, GPU layer 의 동작을 정리한다.
Critical Rendering Path — 6 단계
HTML bytes
↓ parse
DOM tree
↓ + CSS parse
CSSOM tree
↓ combine
Render tree (DOM + 적용된 style, display:none element 제외)
↓ layout
Geometry (각 box 의 x/y/width/height)
↓ paint
Pixel data
↓ composite
화면Step 1-2 — Parsing (DOM + CSSOM)
브라우저가 byte stream 받아 token → node → tree:
- HTML parser 가 DOM 만듦 (how-html-parsing-works 가이드 참조)
- CSS parser 가 CSSOM 만듦
- CSSOM 은 render-blocking — CSS 안 받으면 렌더 X (FOUC 회피)
- script 는 default 로 parser-blocking — DOM 만들기 멈추고 script 실행.
async/defer로 회피
<script src="..."></script> ← parser blocking
<script async src="..."></script> ← download 병행, 받자마자 실행
<script defer src="..."></script> ← download 병행, DOMContentLoaded 직전 실행Step 3 — Render Tree
DOM + CSSOM 결합. 단, 화면에 표시되는 element 만:
display: none제외visibility: hidden는 포함 (자리는 차지)head의 자식은 모두 제외
Step 4 — Layout (= reflow)
각 box 의 정확한 위치·크기 계산:
- document flow, box model (margin/border/padding), float, position
- flex / grid 계산
- text wrap, line break
매우 비쌈 — 한 element 의 size 변경이 자식 + 형제 + 부모 layout 까지 chain. 큰 페이지는 수십 ms.
Layout 을 trigger 하는 변경:
width / height / top / left / right / bottom
margin / padding / border
font-size / line-height
display / position / float
↓
모두 reflow triggerStep 5 — Paint
Geometry 가 결정된 후 실제 pixel 그림:
- background color / image
- border style
- text rendering (font glyph 합성)
- box-shadow / outline
Paint 만 발생하는 변경 (layout 무관):
color
background
visibility
outline
box-shadow
↓
geometry 변경 X → layout skip, paint 만Step 6 — Composite
Paint 한 layer 들을 GPU 에서 합성:
- element 마다 다른 layer 가능 → GPU 가 transform / opacity 만 처리
- CPU 부담 ↓, frame rate ↑
GPU 가 처리하는 변경 (layout / paint 둘 다 skip):
transform: translate / scale / rotate
opacity
↓
60fps 자연 보장. 가장 cheap 한 animation.3 가지 변경의 cost
| 변경 | Pipeline | 비용 |
|---|---|---|
width, height, top | Layout → Paint → Composite | 비쌈 ❌ |
color, background | Paint → Composite | 중간 |
transform, opacity | Composite 만 | 가장 싸다 ✅ |
Animation 은 항상 transform / opacity. top 으로 움직이지 말 것.
60fps 의 16.67ms 예산
1000 ms / 60 = 16.67 ms per frame
여기서 빠질 것:
- browser overhead (input event 처리 등): ~2-3 ms
- 남은 ~13 ms 가 JavaScript + 렌더링
목표:
- JavaScript: < 8 ms
- Style / Layout / Paint / Composite: < 5 ms
초과 시 → frame drop → jank 느낌GPU Layer 만들기 — will-change
/* hover 시 부드러운 transform */
.card {
transition: transform 0.3s;
will-change: transform; ← 미리 GPU layer 만듦
}
.card:hover {
transform: scale(1.05);
}will-change 가 미리 layer 준비. 단, 남용 X — 모든 element 에 박으면 memory 폭주.
암묵적 GPU layer trigger:
transform: translateZ(0)또는translate3dposition: fixed<video>/<canvas>opacity < 1+ animationwill-change: transform | opacity
Layout Thrashing — 가장 흔한 함정
// Bad
for (let i = 0; i < boxes.length; i++) {
boxes[i].style.width = boxes[i].offsetWidth + 10 + 'px';
// ↑ read ↑ write
// read forces layout → write invalidates → next read forces again
// → N 회 layout
}
// Good — read 와 write 분리
const widths = boxes.map(b => b.offsetWidth); // 모든 read 먼저
boxes.forEach((b, i) => b.style.width = widths[i] + 10 + 'px');
// → 1 회 layoutoffsetWidth, getBoundingClientRect, scrollTop 등 — geometry read API. 직전 write 후 호출 시 layout 강제 (= forced synchronous layout).
Core Web Vitals 와 rendering
- LCP (Largest Contentful Paint) — 가장 큰 element render 시간. 2.5s 이하 권장.
- FID / INP (Interaction to Next Paint) — 사용자 input → next frame 시간. 200ms 이하 권장.
- CLS (Cumulative Layout Shift) — 갑작스러운 layout 이동 누적 점수. 0.1 이하 권장.
CLS 의 흔한 원인 — image 의 width/height 미지정 → 로드 후 자리 차지하며 다른 element 밀어냄. aspect-ratio 또는 width + height 명시.
흔한 함정
1. animation 의 top/left
Layout 매 frame trigger. transform 으로 교체.
2. 큰 box-shadow + animation
shadow 가 paint 비용 ↑. 큰 element 의 hover shadow 는 60fps 어려움.
3. forced sync layout
read 와 write 섞기. requestAnimationFrame 으로 분리.
4. font 의 FOIT
font-display: swap 없이 webfont 로드 → 폰트 받는 동안 invisible. swap 으로 fallback 표시 후 교체.
5. excessive will-change
모든 element 에 박으면 메모리 폭주. 실제로 animate 되는 element 만.
참고 자료
- web.dev — Rendering Performance — web.dev
- CSS Triggers (Paul Irish) — csstriggers.com
- Layout Thrashing — web.dev
- Core Web Vitals — web.dev
요약
- Render pipeline 6 단계 — parse → DOM/CSSOM → render tree → layout → paint → composite.
- Layout (reflow) 가 가장 비쌈. width/height/top/left 변경 시 trigger.
- Paint 만 — color/background/visibility/box-shadow.
- Composite 만 (GPU) — transform/opacity. 60fps animation 의 표준.
- 60fps 예산 = frame 당 16.67ms. JS 8ms + 렌더 5ms 안에.
- will-change 로 미리 GPU layer. 남용 X.
- Layout thrashing = read/write 교차로 매번 layout. read 먼저 batch.
- Core Web Vitals (LCP/INP/CLS) — image 크기 명시, animation 은 transform, font-display: swap.