1. 렌더링 최적화란 무엇인가
렌더링 최적화(Rendering Optimization)란
Reflow, Repaint, Composite 단계를 최소화하여 브라우저의 일 부담을 줄이는 것을 의미한다.
쉽게 말해,
“브라우저에게 불필요한 계산과 그리기 작업을 덜 시키는 것.”
JS 로직이 아무리 빠르더라도,
Layout이 계속 다시 계산된다면 프레임 드랍(버벅임)은 피할 수 없다.
2. 핵심 목표
렌더링 최적화의 핵심은 다음 3가지를 줄이는 것이다 👇
목표 설명
| ✅ Reflow 최소화 | DOM 구조 변경, 크기·위치 재계산 줄이기 |
| ✅ Repaint 최소화 | 색상·배경 등 시각 요소만 변경 시 효율화 |
| ✅ Composite 활용 극대화 | GPU가 담당하는 합성 단계로 성능 전환 |
3. Reflow 최소화 전략
(1) DOM 접근 횟수 줄이기
DOM 접근은 매우 느리다.
JS와 브라우저 엔진 간의 “Context Switching”이 매번 발생하기 때문이다.
❌ 나쁜 예
const box = document.getElementById("box");
box.style.width = "100px";
box.style.height = "100px";
box.style.margin = "10px";
✅ 좋은 예
const box = document.getElementById("box");
box.style.cssText = "width:100px;height:100px;margin:10px;";
→ 스타일을 한 번에 변경하면 Reflow가 한 번만 발생한다.
(2) DOM 오프라인 변경
DOM을 직접 수정하는 대신,
문서 외부에서 작업 후 한 번에 삽입하자.
✅ DocumentFragment 사용
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
li.textContent = `항목 ${i}`;
frag.appendChild(li);
}
document.querySelector("ul").appendChild(frag);
→ 브라우저는 1000번이 아니라 단 한 번만 렌더링한다.
(3) 읽기/쓰기 연산 분리 (Layout Thrashing 방지)
❌ 비효율적인 코드
for (let i = 0; i < 1000; i++) {
element.style.width = element.offsetWidth + 1 + "px";
}
✅ 개선
const width = element.offsetWidth;
for (let i = 0; i < 1000; i++) {
element.style.width = width + i + "px";
}
→ Reflow는 “읽기”와 “쓰기”가 섞일 때마다 강제 발생한다.
읽기와 쓰기를 분리하면 성능이 크게 향상된다.
(4) display: none 상태에서 조작
렌더링되지 않는 상태에서는 Layout 계산이 일어나지 않는다.
const list = document.querySelector("ul");
list.style.display = "none";
// 여러 DOM 조작 수행
addItems(list);
list.style.display = "block";
✅ DOM 변경을 모두 마친 후 한 번만 렌더링
4. Repaint 최소화 전략
Repaint는 화면에 “색을 다시 칠하는 단계”다.
Reflow보단 덜 무겁지만, 잦은 Repaint도 CPU를 많이 잡아먹는다.
(1) 색상/배경 변경 최소화
element.style.backgroundColor = "red";
element.style.color = "white";
→ 이런 연속된 스타일 변경은 Paint를 여러 번 발생시킨다.
→ 클래스 변경으로 한 번에 적용하자.
element.classList.add("highlight");
(2) visibility vs display
속성 효과 Reflow 발생 여부
| display: none | 완전히 제거 | ✅ 발생 |
| visibility: hidden | 영역은 유지 | ❌ 없음 |
간단히 숨길 때는 visibility를 사용하면 더 빠르다.
(3) will-change 속성 사용
브라우저에게 “이 요소는 곧 변할 것”을 미리 알려준다.
.card {
will-change: transform, opacity;
}
✅ GPU가 미리 해당 레이어를 생성
✅ 애니메이션 시작 시 부드럽게 작동
단, 남용은 금물 — 너무 많은 will-change는 GPU 메모리를 낭비한다.
5. Composite 단계 활용
GPU 합성(Compositing) 은 가장 빠른 렌더링 단계다.
즉, Layout이나 Paint 없이 “합성만 다시” 하는 게 가능하다.
GPU 합성으로 동작하는 CSS 속성들
- transform
- opacity
- filter
- will-change
- translate3d, scale3d 등 3D 변환
✅ 이 속성들은 Reflow/Repaint 없이 GPU 레벨에서 즉시 반영된다.
(1) transform 애니메이션 사용
❌ 나쁜 예 (Reflow 발생)
.box {
position: relative;
animation: move 1s linear infinite;
}
@keyframes move {
from { left: 0; }
to { left: 100px; }
}
✅ 좋은 예 (GPU 처리)
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
→ transform 기반 애니메이션은 Layout 변경이 없으므로 훨씬 부드럽다.
(2) opacity를 이용한 전환
❌ display로 전환 시 Layout 발생
✅ opacity 전환 시 Composite만 발생
.fade {
transition: opacity 0.3s ease;
}
6. 이미지 렌더링 최적화
1️⃣ 적절한 포맷 사용
- 사진 → WebP, AVIF
- 아이콘 → SVG
2️⃣ Lazy Loading 적용
<img src="img.jpg" loading="lazy" alt="이미지" />
3️⃣ Responsive Image (srcset)
화면 크기에 맞게 이미지 크기 자동 조정
<img src="img-800.jpg" srcset="img-400.jpg 400w, img-800.jpg 800w" sizes="(max-width:600px) 400px, 800px" />
7. 폰트 렌더링 최적화
폰트는 렌더링 지연의 주범 중 하나다.
특히 웹폰트 로딩 시 “FOIT (Flash of Invisible Text)” 문제가 발생한다.
✅ 해결책:
font-display: swap;
→ 로딩 중에는 시스템 폰트로 먼저 렌더링 후,
폰트 로드 완료 시 교체한다.
8. JS 기반 렌더링 최적화
(1) requestAnimationFrame()
JS 애니메이션을 구현할 때
setInterval 대신 requestAnimationFrame을 사용하자.
function animate() {
box.style.transform = `translateX(${pos++}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
✅ 브라우저의 프레임 타이밍(60fps)에 맞춰 호출됨
✅ 렌더링 루프와 완벽히 동기화
(2) requestIdleCallback()
백그라운드 작업을 브라우저가 “한가할 때” 처리하도록 한다.
requestIdleCallback(() => {
heavyComputation();
});
✅ 사용자의 인터랙션과 충돌하지 않음
✅ React 18의 concurrent mode 내부에도 동일한 원리 사용
9. 프레임워크 기반 최적화 (React / Next.js)
(1) React
- Virtual DOM이 Reflow/Repaint를 최소화
- 불필요한 리렌더링 방지를 위해 memo(), useMemo(), useCallback() 활용
(2) Next.js
- SSR + Static Rendering 으로 초기 렌더링 속도 향상
- 이미지 자동 최적화 (next/image)
- 코드 스플리팅 및 Lazy Component 로드
10. 실무 체크리스트
✅ Reflow 최소화
- DOM 접근/조작 최소화
- Layout Thrashing 방지
- 한 번에 스타일 변경
✅ Repaint 최소화
- 클래스 단위 스타일 변경
- visibility 활용
- will-change 적절히 사용
✅ GPU 활용
- transform, opacity 기반 애니메이션
- position: fixed 요소 최소화
✅ 리소스 최적화
- 이미지 Lazy Loading
- 폰트 swap 적용
- async/defer로 JS 비동기 로드
11. 마무리
렌더링 최적화는 단순히 "속도를 높이는 테크닉"이 아니라,
브라우저의 구조를 이해한 개발자의 사고방식이야.
“성능 최적화는 코드를 바꾸는 게 아니라,
브라우저가 더 적은 일을 하도록 돕는 것이다.”
'frontend > javascript' 카테고리의 다른 글
| 🟨 2-26. 메모리 누수와 성능 저하를 막는 실전 관리 전략 (JS & 브라우저 관점) (0) | 2025.11.07 |
|---|---|
| 🟨 2-25. 자바스크립트 엔진 내부 구조 — V8 엔진이 코드를 처리하는 진짜 방식 (0) | 2025.11.07 |
| 🟨 2-23. 브라우저 렌더링 과정 — 자바스크립트, CSS, DOM이 함께 그려지는 진짜 순서 (0) | 2025.11.07 |
| 🟨 2-22. 이벤트 루프와 비동기 흐름의 시각적 이해 — 브라우저의 내부 작동 구조 (0) | 2025.11.07 |
| 🟨 2-21. 비동기 프로그래밍 완전정복 — Promise, async/await, 그리고 이벤트 루프의 협업 구조 (0) | 2025.11.07 |