'raF()'의 등장 배경, 원래, 사용 방법에 대해 정리했습니다.
🔽 웹 애니메이션
웹에서 애니메이션은 단순한 장식이 아니라, 사용자의 경험을 매끄럽게 만들어 주는 중요한 요소입니다.
예를 들어, 버튼을 클릭했을 때 부드럽게 색이 변하거나, 로딩 중에 프로그래스바가 자연스럽게 움직이는 것은 모두 애니메이션 덕분이죠.
CSS의 `transition`, `animation`, `transform` 만으로도 다양한 애니메이션을 만들 수 있습니다. 하지만 CSS 애니메이션만으로는 한계가 있습니다. 예를 들어, 스크롤 위치에 따라 요소 크기를 동적으로 바꾸거나, 실시간 데이터(예: 프로그래스바, 그래프) 를 반영하는 애니메이션은 JS 제어가 필요합니다.
전통적으로는 `setInterval` 같은 타이머 기반 방식으로 애니메이션을 구현했지만, 브라우저 렌더링 방식과 꼭 맞아떨어지지 않아 종종 '끊김(jank)'이 발생했습니다. 이 문제를 해결하기 위해 등장한 것이 바로 `requestAnimationFrame(rAF)` 입니다.
그렇다면, 왜 rAF가 애니메이션에 더 적합한지 하나씩 살펴볼까요?
🔽 브라우저 렌더링
브라우저는 단순히 HTML/CSS/JS 코드를 읽는 것에 그치지 않고, 이를 화면에 그려내는 복잡한 과정을 거칩니다.
크게 보면 다음 단계를 반복합니다.
1. DOM 생성 – HTML을 파싱해 문서 구조를 트리 형태로 DOM 생성
2. CSSOM 생성 – CSS를 파싱해 스타일 규칙을 트리 형태의 CSSOM 생성
3. 렌더 트리(Render Tree) - DOM과 CSSOM을 매칭해 실제로 화면에 표시될 노드를 계산하여 렌더 트리(Render Tree)를 생성
4. 레이아웃(Layout) – 브라우저는 객체들의 위치와 크기 등을 계산
5. 페인트(Paint) – 브라우저는 렌더 트리를 사용하여 실제로 화면에 픽셀을 출력 (객체가 화면에 그려짐)
6. 합성(Composite) – 브라우저는 화면에 출력되는 레이어를 합쳐 실제 화면에 그림
이 과정을 매 순간 빠르게 반복하면서 화면을 ‘움직이듯’ 보여주는 것이 바로 렌더링입니다.
🔽 브라우저 프레임
이때 화면을 한 번 그려내는 단위를 프레임(Frame) 이라고 부릅니다.
즉, 프레임은 영화의 한 장면, 만화의 한 컷과 같은 개념입니다.
브라우저는 여러 프레임을 빠르게 이어붙여 보여줌으로써 부드러운 애니메이션 효과를 만듭니다.
🔽 60FPS(Frame Per Second) ?
FPS는 1초 동안 보여지는 프레임 수를 의미합니다.
- 30FPS → 1초에 30장 그림 (약간 끊겨 보임)
- 60FPS → 1초에 60장 그림 (부드러운 움직임)
브라우저는 일반적으로 이 렌더링 과정이 초당 60번(60FPS) 실행되는 것을 목표로 합니다.
즉, 한 프레임을 그리는 데 약 16.666ms(1000ms ÷ 60) 의 시간이 주어집니다.
다시 말하면, 16.7ms 안에 JS 실행, 스타일 계산, 레이아웃, 페인트까지 모두 끝내야 한다는 뜻입니다.
🔽 60FPS 는 왜 맞춰야 할까요?
만약 16.7ms 안에 렌더링 과정을 끝내지 못하면, 그 프레임은 건너뛰어집니다.
이렇게 되면 화면이 순간적으로 멈춘 것처럼 보이는 jank(끊김) 현상이 발생합니다.
그래서 애니메이션을 부드럽게 만들려면, 브라우저의 프레임 사이클에 맞춰 수행하는 것이 중요합니다.
그리고 그 타이밍을 정확히 잡아주는 API가 바로 `requestAnimationFrame` 입니다.
🔽 setInterval 로 애니메이션 구현하기
타이머 기반 함수(`setInterval`, `setTimeout`)를 사용해서 애니메이션을 구현할 수 있습니다.
직접 애니메이션을 구현하면 아래와 같이 일정한 간격(여기서는 16.7ms)으로 함수를 호출합니다.
const animation = () => {
/* 스타일 조정 스크립트 */
}
// 1초에 60번 무한 반복
setInterval(animation, 1000 / 60) // 60FPS를 목표로 16.67ms마다 실행
🔽 타이머 함수의 문제점
타이머 기반 함수(`setInterval`, `setTimeout`)는 일정한 간격으로 콜백을 실행하지만, 이 실행 시점은 브라우저의 렌더링 사이클과 정확히 맞지 않습니다.

`setTimeout`은 보통 16ms 간격(60FPS 목표)으로 실행되지만, 브라우저의 렌더링 타이밍과 정확히 동기화되지는 않습니다. 그 결과 자바스크립트 연산이 조금만 길어져도 화면에 적용되는 시점이 밀리면서 레이아웃(Layout) → 페인트(Paint) → 합성(Composite) 과정이 프레임 사이클(16.7ms)을 넘어가 버릴 수 있습니다. 이렇게 되면 해당 프레임은 아예 건너뛰어지고, 화면은 순간적으로 멈춘 듯한 프레임 드랍(jank) 현상을 보이게 됩니다. 특히 CPU 부하가 많을 때나 여러 작업이 동시에 실행될 때는 이 현상이 더 자주 발생합니다. 프레임 드랍이 반복되면 애니메이션은 매끄럽지 못하고 버벅이는 것처럼 보입니다.
‼ 이러한 현상이 일어날 수 있는 이유는 자바스크립트의 콜 스택(call stack)은 싱글 스레드 이기 때문이다
이렇듯, 타이머 함수는 단순 반복에는 괜찮지만, 애니메이션처럼 렌더링 타이밍에 민감한 작업에는 적합하지 않다는 문제가 있습니다.
이런 지연(delay) 문제를 해결하기 위해 브라우저는 렌더링 직전에 실행되는 전용 API를 마련했는데, 그것이 바로 `requestAnimationFrame(rAF)`입니다.
rAF는 브라우저가 실제로 화면을 다시 그리기 직전에 호출되므로, 불필요한 연산을 줄이고 렌더링과 정확히 맞물려 더 부드러운 애니메이션을 보장할 수 있습니다.
1. 렌더링과 비동기
타이머는 브라우저의 렌더링 사이클과 무관하게 동작하여 브라우저의 렌더링 타이밍과 동기화되지 않습니다.
따라서 호출 시점과 실제 화면에 그려지는 시점 사이에 차이가 발생하여 프레임 드랍(jank)이 생길 수 있습니다.
CPU 부하가 많을 때는 더 쉽게 프레임 드랍(jank)이 생깁니다.
2. 정확도 문제
자바스크립트의 이벤트 루프와 다른 작업(예: 연산, I/O)에 의해 지연이 발생할 수 있습니다.
→ 지정한 16.7ms 간격이 실제로는 더 길어지는 경우가 흔합니다.
3. 리소스 낭비
브라우저 탭이 비활성화된 경우에도 계속 실행됩니다.
→ 보이지 않는 애니메이션도 계속 그려 CPU와 배터리를 낭비하게 됩니다.
4. 복잡한 제어 한계
애니메이션의 프레임 단위 제어, 프레임 스킵 방지 같은 세밀한 최적화가 어렵습니다.
🔽 requestAnimationFrame
`requestAnimationFrame`은 브라우저가 다음 프레임을 그리기 직전에 함수를 호출하도록 예약하는 API입니다.
즉, 개발자가 임의로 간격(ex: 16ms)을 지정하는 것이 아니라, 브라우저가 렌더링 사이클(보통 16.6ms)에 맞춰 최적의 시점에 함수를 실행합니다.

위 그림처럼, JS 실행 → 레이아웃 → 페인트 → 합성 과정이 끝난 직후 다음 프레임이 준비되는데, rAF는 바로 이 타이밍에 맞춰 콜백을 실행합니다. 덕분에 불필요한 호출을 줄이고, 프레임 사이클에 정확히 동기화되어 부드러운 애니메이션을 만들 수 있습니다.
🔽 Animation frames 큐에서 처리
이벤트 루프에서 일반 태스크(Task queue)와 마이크로태스크(Microtask queue)가 모두 처리된 이후, 브라우저는 Animation frames 큐를 실행합니다. 이 큐에는 `requestAnimationFrame`으로 등록된 콜백들이 담기며, 브라우저가 실제로 화면을 다시 그리기 직전에 호출됩니다. 따라서 애니메이션 로직은 브라우저의 렌더링 타이밍과 정확히 동기화되어 실행되며, 불필요한 연산을 줄이고 매끄러운 화면 업데이트를 보장할 수 있습니다.

🔽 requestAnimationFrame의 장점
1. 백그라운드 동작 중지
브라우저 탭이 비활성화되면 자동으로 실행이 멈추어 불필요한 연산을 줄이고 CPU·배터리를 절약할 수 있습니다.
2. 디스플레이 주사율에 맞게 호출
모니터 주사율(60Hz, 120Hz 등)에 맞춰 자동으로 실행 주기가 조정되어 다양한 환경에서도 부드러운 애니메이션을 보여줍니다.
🔽 requestAnimationFrame의 단점
단순 반복 작업처럼 렌더링과 무관한 경우에는 오히려 적합하지 않으며, 오래된 브라우저에서는 폴리필이 필요할 수 있습니다.
🔽 requestAnimationFrame 기본 사용법
const animation = () => {
/* 애니메이션 처리 스크립트 */
requestAnimationFrame(animation) // 다음 프레임 직전에 다시 실행 예약
}
requestAnimationFrame(animation);
requestAnimationFrame 안에서 자기 자신을 다시 호출하는 방식으로 반복 애니메이션을 만듭니다.
cancelAnimationFrame(id)을 사용하면 멈출 수도 있습니다.
🔽 setInterval vs rAF 두 방식 비교
두 방식 모두 애니메이션을 구현할 수 있지만, 접근 방식이 다릅니다.
`setInterval`은 단순히 일정한 시간 간격으로 콜백을 실행하기 때문에 브라우저의 렌더링 주기와 어긋날 수 있고, 이로 인해 프레임 드랍이 발생하기 쉽습니다. 반면 `requestAnimationFrame`은 브라우저가 실제로 화면을 다시 그리기 직전에 맞춰 실행되므로, 보다 자연스럽고 효율적인 애니메이션을 보장합니다.
따라서 반복 작업에는 `setInterval`이 유용하지만, 화면 렌더링과 밀접한 애니메이션에는 `rAF`가 훨씬 더 적합합니다.

| 구분 | setInterval | requestAnimationFrame |
| 실행 주기 | 일정 시간 간격 | 브라우저 렌더링 직전 |
| 성능 | 불필요한 호출 발생 | 효율적, 자원 절약 |
| 부드러움 | 프레임 드랍 발생 가능 | 브라우저 최적화로 자연스러움 |
| 탭 비활성화 시 | 계속 실행됨 | 자동으로 멈춤 |
🔽 참고자료
- https://inpa.tistory.com/entry/%F0%9F%8C%90-requestAnimationFrame-%EA%B0%80%EC%9D%B4%EB%93%9C
- https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/
- https://hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank/
'React' 카테고리의 다른 글
| 선언적으로 Modal 시스템 구현하기 (0) | 2025.10.20 |
|---|---|
| useQRCode를 만들어보자 (0) | 2025.09.30 |
| [아키텍처] FSD 아키텍처 : Feature-Sliced Design (1) | 2025.06.12 |
| [UX 개선] 낙관적 업데이트(Optimistic Update) (0) | 2025.06.03 |
| [TanStack Query] 서버 상태 관리 라이브러리 (0) | 2025.06.02 |