frontend/javascript

🟨 2-24. 렌더링 최적화 실무 전략 — Layout, Paint, Composite 성능 개선법 총정리

mirabo01 2025. 11. 7. 08:56

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. 마무리

렌더링 최적화는 단순히 "속도를 높이는 테크닉"이 아니라,
브라우저의 구조를 이해한 개발자의 사고방식이야.

“성능 최적화는 코드를 바꾸는 게 아니라,
브라우저가 더 적은 일을 하도록 돕는 것이다.”