
1편에서는 브라우저가 화면을 만드는 기본 구조를 살펴봤다.
HTML을 읽는다.
CSS를 읽는다.
Render Tree를 만든다.
Layout을 계산한다.
Paint를 수행한다.
그리고 여기서 중요한 사실 하나를 확인했다.
레이아웃(Layout) 계산은 브라우저에서 비용이 큰 작업이다.
그래서 프론트엔드 성능 최적화에서 항상 등장하는 원칙이 있다.
DOM 읽기와 DOM 쓰기를 분리하라.
하지만 여기서 질문이 하나 생긴다.
레이아웃과 페인트 이후에는 무슨 일이 일어날까?
그리고 왜 어떤 애니메이션은 부드럽고 어떤 애니메이션은 버벅일까?
이 질문을 이해하려면 렌더링 과정의 마지막 단계를 알아야 한다.
브라우저가 화면을 완성하는 마지막 단계
렌더링 과정에는 마지막 단계가 하나 더 있다.
바로 Composite 단계다.
렌더링 과정을 단순하게 표현하면 다음과 같다.
각 단계의 역할은 다음과 같다.
Layout
요소의 위치와 크기를 계산한다.
Paint
텍스트, 색상, 그림자 같은 실제 픽셀을 그린다.
Composite
이미 그려진 레이어들을 합쳐 최종 화면을 만든다.
여기서 중요한 점이 있다.
Composite 단계는 GPU에서 처리될 수 있다.
그래서 어떤 애니메이션은 매우 부드럽게 보이고
어떤 애니메이션은 버벅인다.
브라우저는 화면을 레이어로 나누어 처리한다
브라우저는 모든 요소를 한 번에 그리는 것이 아니라
여러 개의 레이어(layer) 로 나누어 처리할 수 있다.
예를 들어 웹 페이지에 다음 요소들이 있다고 생각해보자.
배경 이미지
텍스트
버튼
애니메이션 요소
브라우저는 이 모든 것을 하나의 그림으로 그릴 수도 있지만
성능을 위해 일부 요소를 별도의 레이어로 분리한다.
레이어가 분리되면 다음과 같은 일이 가능해진다.
전체 화면을 다시 그리지 않아도 된다.
특정 레이어만 이동시킬 수 있다.
GPU에서 빠르게 처리할 수 있다.
이 과정이 바로 Composite 단계다.
왜 transform 애니메이션이 부드러운가
다음 두 CSS 코드를 비교해보자.
left: 200px
transform: translateX(200px)
두 코드 모두 요소를 오른쪽으로 이동시킨다.
하지만 브라우저 내부에서는 전혀 다른 일이 일어난다.
left 속성을 사용하는 경우
left 속성은 레이아웃 속성이다.
그래서 값이 변경되면 브라우저는 다음 과정을 수행한다.
Paint 다시 수행
Composite
즉 레이아웃 계산부터 다시 시작한다.
그래서 비용이 크다.
transform 속성을 사용하는 경우
transform은 레이아웃을 변경하지 않는다.
요소의 위치 계산 자체는 그대로 두고
이미 그려진 레이어를 이동시키는 방식이다.
그래서 브라우저는 다음 단계만 수행한다.
레이아웃 계산도 다시 하지 않고
픽셀도 다시 그리지 않는다.
그래서 애니메이션이 훨씬 부드럽다.
그래서 등장하는 성능 원칙
프론트엔드 개발에서는 항상 이런 말을 한다.
애니메이션은 transform과 opacity로 만들어라
그 이유는 단순하다.
이 속성들은 대부분 Composite 단계에서 처리되기 때문이다.
즉 Layout과 Paint 단계를 건너뛸 수 있다.

will-change라는 CSS 속성
CSS에는 will-change라는 속성이 있다.
이 속성은 브라우저에게 다음과 같은 의미를 전달한다.
이 요소는 곧 변경될 가능성이 있으니 미리 준비하라.
예를 들어 이런 코드다.
will-change: transform;
이 속성이 있으면 브라우저는
해당 요소를 미리 별도의 레이어로 분리할 가능성이 높다.
그래서 애니메이션이 더 부드럽게 실행될 수 있다.
하지만 주의할 점이 있다.
레이어는 GPU 메모리를 사용한다.
그래서 너무 많은 요소에 will-change를 사용하면
오히려 성능이 떨어질 수 있다.
requestAnimationFrame은 무엇을 하는 함수인가
JavaScript 애니메이션에서는
requestAnimationFrame()이라는 함수가 자주 등장한다.
이 함수는 브라우저에게 다음과 같은 의미를 가진다.
다음 화면이 그려지기 직전에 이 코드를 실행하라.
예를 들어 이런 코드가 있다고 생각해보자.
setInterval(() => {
element.style.left = x + "px"
}, 16)
이 코드는 약 60fps로 실행된다.
하지만 브라우저 렌더링 타이밍과 정확히 맞지 않을 수 있다.
그래서 불필요한 Layout 계산이 발생할 수 있다.
requestAnimationFrame을 사용하는 경우
requestAnimationFrame(() => {
element.style.transform = `translateX(${x}px)`
})
이 함수는 브라우저의 렌더링 사이클에 맞춰 실행된다.
즉 다음 프레임이 그려지기 직전에 실행된다.
그래서 애니메이션이 더 자연스럽게 동작한다.
DevTools에서 레이어를 확인하는 방법
Chrome DevTools에서는
Composite Layer를 직접 확인할 수 있다.
방법은 다음과 같다.
DevTools 열기
Rendering 탭 활성화
Layer borders 옵션 체크
그러면 화면에 레이어 경계선이 표시된다.
이렇게 하면 어떤 요소가 GPU 레이어로 분리되었는지 확인할 수 있다.
핵심 정리
브라우저 렌더링 과정에는 Composite 단계가 존재한다.
브라우저는 화면을 여러 레이어로 나누어 처리할 수 있다.
transform과 opacity는 Composite 단계에서 처리될 수 있다.
requestAnimationFrame은 브라우저 렌더링 타이밍에 맞춰 실행된다.
이 구조를 이해하면
왜 어떤 애니메이션은 부드럽고
어떤 애니메이션은 버벅이는지 이해할 수 있다.
다음 글에서 다룰 내용
지금까지는 브라우저 렌더링 구조와
성능 최적화 원칙을 살펴봤다.
하지만 실제 서비스에서는 또 다른 문제가 등장한다.
DOM 요소가 매우 많아질 때
리스트 UI가 길어질 때
실시간 업데이트가 발생할 때
이때 성능 문제는 어떻게 해결할까?
다음 글에서는 다음 내용을 살펴본다.
Virtual DOM이 등장한 이유
리스트 UI 성능 문제
Virtual Scrolling
대규모 UI에서 Layout 비용 줄이기
이제 렌더링 원리를 이해했으니
다음 단계는 실제 서비스 성능 구조다.

'웹개발' 카테고리의 다른 글
| [CSS Rendering Path][4편](마지막) 웹이 느려질 때 DevTools로 성능 문제 찾는 방법 (1) | 2026.03.18 |
|---|---|
| [CSS Rendering Path][3편] DOM이 많아질수록 웹이 느려지는 이유 (Virtual DOM과 리스트 UI 성능) (0) | 2026.03.17 |
| [CSS Rendering Path][1편] CSS를 다루는 사람이라면 한 번은 반드시 알아야 하는 것: Reflow와 Repaint가 실제로 일어나는 순간 (1) | 2026.03.14 |
| 앱 개발 1인 창업, 왜 실패할까? 성공한 개발자들이 공통으로 지킨 5가지 법칙 (2) | 2025.09.08 |
| Atropos JS (1) | 2023.05.05 |