setInterval은 애니메이션에 실패하고, requestAnimationFrame이 정답인 이유
웹에서 부드러운 애니메이션을 구현하려 할 때, setInterval은 가장 먼저 떠오르는 손쉬운 방법입니다. 하지만 곧 한계에 부딪힙니다. 애니메이션이 뚝뚝 끊기거나, 다른 탭에 다녀오면 시간이 엉망이 될 수 있습니다.
이 글에서는 setInterval이 본질적으로 가진 문제점과, 왜 requestAnimationFrame (rAF)이 현대 웹 애니메이션의 표준이 되었는지 브라우저의 렌더링 원리와 함께 자세히 알아보겠습니다.
1. 오래된 방식: setInterval 애니메이션의 한계
setInterval(callback, 16)은 "16ms마다 callback을 실행하라"는 명령입니다. 하지만 이 방식은 여러 단점이 있습니다.
문제 1: 부정확한 delay와 이벤트 루프
delay는 애니메이션 프레임을 설정하는 것이 아니라, 콜백 함수를 '태스크 큐(Task Queue)'에 등록하는 최소 시간일 뿐입니다. 자바스크립트는 싱글 스레드이며, 이벤트 루프에 의해 동작합니다.
setInterval의 콜백은 매크로태스크로 분류됩니다. 이벤트 루프는 콜 스택(Call Stack)이 비어 있을 때만 태스크 큐에서 작업을 가져와 실행합니다.
만약 콜 스택이 delay가 지난 시점에 다른 복잡한 계산으로 바쁘다면, callback 실행은 무기한 지연됩니다.
문제 2: 우선순위 문제
이벤트 루프에는 마이크로태스크(Microtask) 큐가 존재합니다. Promise.then()이나 await 같은 작업들이 여기에 속합니다.
이벤트 루프의 실행 순서: (1) 콜 스택 비우기 → (2) 마이크로태스크 큐 비우기 → (3) 매크로태스크 큐에서 하나 가져오기
setInterval은 매크로태스크이므로, 아무리 delay가 짧아도 당장 실행해야 할 마이크로태스크(Promise 등)가 있다면 무조건 뒤로 밀립니다. 실제 서비스 환경에서 정확한 실행을 보장할 수 없는 이유입니다.
문제 3: 비효율적인 리소스 사용
setInterval은 페이지가 비활성화되거나, 창이 최소화되어도 백그라운드에서 계속 실행됩니다. 물론 브라우저가 리소스를 아끼기 위해 실행 간격을 1초에 한 번꼴로 늦춰버리지만(Throttling), 그럼에도 불필요한 CPU와 배터리를 소모가 발생합니다.
2. 현대적 해결책: requestAnimationFrame (rAF)
requestAnimationFrame (rAF)은 이름 그대로 "애니메이션 프레임을 요청"하는, 애니메이션을 위해 특별히 설계된 API입니다.
window.requestAnimationFrame(callback)을 호출하면 브라우저에게 이렇게 요청합니다.
"다음 화면을 그리기 직전에 callback 함수를 실행해주세요."
이것이 setInterval의 모든 문제를 해결합니다.
장점 1: 완벽한 타이밍
rAF는 사용자의 모니터 주사율에 맞춰 실행됩니다. 60Hz 모니터에서는 1초에 60번, 144Hz 모니터에서는 1초에 144번 호출됩니다. 브라우저의 렌더링 주기와 완벽하게 동기화되므로 단 하나의 프레임도 낭비하지 않아 극도로 부드러운 애니메이션을 만듭니다.
장점 2: 높은 실행 우선순위
이벤트 루프에는 rAF 콜백만을 위한 전용 큐인 'Animation Frames'가 존재합니다.
이 큐의 실행 우선순위는 매크로태스크(setInterval)보다 높고 마이크로태스크(Promise)보다는 낮습니다.
정확한 이벤트 루프 순서: (1) 콜 스택 → (2) 마이크로태스크 큐 → (3) Animation Frames (rAF) 큐 → (4) 매크로태스크 큐
즉, setInterval보다 항상 먼저 실행되는 것이 보장됩니다.
장점 3: 최고의 효율 (자동 일시정지)
rAF는 페이지가 비활성화 상태일 때 브라우저가 리페인트 작업을 멈추면, 콜백 호출도 함께 멈춥니다. 불필요한 리소스를 전혀 낭비하지 않습니다. 탭으로 돌아오면 즉시 이어서 실행됩니다.
장점 4: 고정 timestamp 제공
rAF의 콜백 함수는 DOMHighResTimeStamp를 인자로 받습니다. 이 값은 애니메이션 시작부터 현재까지의 시간을 1/1000ms 단위로 알려주어, 정밀한 시간을 기반으로 애니메이션을 구현할 수 있게 해줍니다.
3. 브라우저 렌더링과 rAF의 실행 시점
rAF가 왜 부드러운지 이해하려면 브라우저 렌더링 과정을 알아야 합니다.
- DOM/CSSOM 생성: HTML과 CSS를 파싱하여 렌더링의 기본 재료를 만듭니다.
- Layout (Reflow): 각 요소의 화면상 위치와 크기를 계산합니다.
- Paint (Repaint): 요소를 픽셀 단위로 그리고 여러 개의 레이어를 만듭니다.
- Composite: 이 레이어들을 순서대로 합쳐 최종적으로 화면에 표시합니다.
requestAnimationFrame의 콜백은 바로 1, 2, 3 단계가 실행되기 직전에 호출됩니다.
즉, rAF 콜백 내에서 스타일을 변경하면, 브라우저는 이 변경 사항을 즉시 알아채고 리페인트 주기에 바로 반영하여 화면에 그려줍니다.
4. requestAnimationFrame 사용예
rAF는 한 번만 실행되므로, 애니메이션을 지속하려면 콜백 함수 내에서 스스로를 재귀적으로 호출해야 합니다.
const animate = ({ timing, draw, duration }) => {
// 1. 시작 시간을 정확히 기록
const start = performance.now();
// 2. rAF 콜백 함수 정의
const animationCallback = (time) => {
// time은 rAF가 제공하는 현재 시각 (DOMHighResTimeStamp)
// 3. 시간 경과 비율 계산 (0 ~ 1 사이)
let timeFraction = (time - start) / duration;
if (timeFraction > 1) {
timeFraction = 1;
}
// 4. 타이밍 함수를 적용해 실제 진행률 계산
const progress = timing(timeFraction);
// 5. 계산된 진행률로 실제 DOM을 그림
draw(progress);
// 6. 백프로가 안됐으면 다음 프레임 요청
if (timeFraction < 1) {
requestAnimationFrame(animationCallback);
}
};
// 7. 최초의 프레임 요청
requestAnimationFrame(animationCallback);
};
//사용 예시
// 타이밍 함수
const timing = (timeFraction) => timeFraction;
// 그리기 함수
const draw = (progress) => {
const element = document.getElementById('myElement');
element.style.left = progress * 500 + 'px';
};
// 애니메이션 실행
animate({
timing,
draw,
duration: 1000,
});
5. 주의점: requestAnimationFrame과 Node.js
requestAnimationFrame은 Node.js 환경에 존재하지 않습니다.
- rAF는 window 객체에 속한 브라우저 전용 API입니다.
- 이 API의 목적은 브라우저의 시각적 리페인트 주기에 작업을 동기화하는 것입니다.
- Node.js는 서버 사이드 런타임이므로 window 객체도, 모니터 주사율도, 렌더링 파이프라인도 없습니다.
따라서 rAF는 서버에서 아무런 의미가 없으며, 호출 시 ReferenceError: requestAnimationFrame is not defined 오류가 발생합니다.
만약 브라우저와 Node.js에서 호환되는 코드가 필요하다면, raf 같은 npm 패키지를 사용해 setTimeout(callback, 16)으로 동작을 흉내 내는(Polyfill) 방법이 있습니다.
결론
setInterval은 타이머일 뿐, 애니메이션을 위한 도구가 아닙니다.
브라우저에서 모든 종류의 시각적 업데이트(CSS 변경, , 타이머 숫자 변경 등)를 수행할 때는 requestAnimationFrame을 사용하는 것이 표준이고 브라우저와 완벽하게 동기화되어 최고의 성능, 정확성, 리소스 효율성을 보장하는 유일한 방법입니다.
Reference
https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
Window: requestAnimationFrame() method - Web APIs | MDN
An unsigned long integer value, the request ID, that uniquely identifies the entry in the callback list. You should not make any assumptions about its value. You can pass this value to window.cancelAnimationFrame() to cancel the refresh callback request. W
developer.mozilla.org
https://www.devdic.com/javascript/reference/window/method:2249/requestAnimationFrame()
requestAnimationFrame()
requestAnimationFrame() 메소드는 비동기 함수로서 CSS 애니메이션으로는 처리가 어렵거나 canvas, SVG 등의 애니메이션을 직접 구현하고자 할 때 사용된다. 이 메소드는 애니메이션 구현에 필요한 타임
www.devdic.com
https://ko.javascript.info/js-animation
JavaScript animations
ko.javascript.info
https://flaviocopes.com/requestanimationframe/
The requestAnimationFrame() guide
Learn the API to perform animations and schedule event in a predictable way
flaviocopes.com
https://developer.mozilla.org/ko/docs/Web/API/Window/requestAnimationFrame
Window: requestAnimationFrame() 메서드 - Web API | MDN
unsigned long 정수 값으로, 콜백 목록 내 항목을 고유하게 식별하는 요청 ID 입니다. 이 값의 형태나 패턴에 대해 어떤 가정도 해서는 안 됩니다. 이 값을 window.cancelAnimationFrame()에 전달하여 등록된
developer.mozilla.org
'FE' 카테고리의 다른 글
| [멋사] 2025 4호선톤 회고 (0) | 2025.11.17 |
|---|---|
| 전역 상태 라이브러리 (1) | 2025.11.17 |
| 레이아웃 스래싱(Layout Thrashing) (0) | 2025.10.26 |
| 자바스크립트 기초: 객체, DOM, 이벤트 핸들링 (0) | 2025.10.18 |
| 웹 소켓 (Web Socket) 제대로 이해하기 (0) | 2025.10.15 |