한양과학기술고등학교

에듀테크 프로젝트 대시보드

requestAnimationFrame

애니메이션은 "몇 번 불렸는가"가 아니라 "얼마나 시간이 흘렀는가"로 움직여야 한다

requestAnimationFrame(이하 rAF)은 브라우저가 다음 화면을 그리기 직전에 딱 한 번 불러주는 콜백이다. 콜백은 현재 시각(타임스탬프)을 인자로 받으므로, 그 시각으로 실제 경과 시간을 계산해 위치를 정하면 프레임이 몇 번 불렸든 항상 정확한 자리에 그려진다. 반대로 "콜백 한 번 = 고정 걸음 한 칸"으로 짜는 초보적인 setInterval 애니메이션은 콜백이 밀리면 그만큼 뒤처진다. 아래에서 직접 재생하고, 메인 스레드를 블로킹해 그 차이를 확인해 보자.

실제 경과 시간 (기준)
0.0 s
rAF 실측 FPS
0
setInterval 호출 수 (기대 대비)
0 / 0

rAF 프레임 간격 매 콜백 사이 시간(ms) · 60fps 예산 16.7ms를 넘으면 빨간 막대(프레임 드롭)

델타타임 보정

파란 기준선과 초록 rAF 점은 항상 겹친다. 매 콜백마다 timestamp - startTime으로 실제 경과 시간을 다시 계산해 위치를 정하기 때문에, 콜백이 늦게 불려도 "늦게 부른 만큼" 더 멀리 이동해 제자리를 찾는다.

화면 주사율에 동기화

rAF는 브라우저가 실제로 다음 화면을 그리기 직전에 호출된다. 대부분 모니터가 60Hz라 약 16.7ms 간격이 되고, 탭이 백그라운드로 가면 브라우저가 rAF 호출 자체를 멈춰 배터리를 아낀다. 반면 setInterval은 화면 주사와 무관하게 그냥 정해진 시간마다 울리는 알람일 뿐이다.

고정 스텝의 함정

빨간 점(setInterval)은 "콜백이 불릴 때마다 한 칸"만 이동한다. 메인 스레드가 1.5초간 막히면 그 사이 예정됐던 호출들이 대부분 사라지고 딱 1~2번만 불리므로, 블로킹이 끝나도 실제 경과 시간만큼 따라잡지 못하고 계속 뒤처진 채로 남는다.

핵심: 애니메이션 로직은 "몇 번 호출됐는가"가 아니라 "얼마나 시간이 지났는가"를 기준으로 짜야 한다. 게임 엔진에서도 이를 delta time 기반 이동이라 부르며, 프레임이 들쭉날쭉해도(고사양 PC의 144fps든 저사양의 30fps든) 물체의 실제 이동 속도는 동일하게 유지된다.

💡 그럼 setInterval은 언제 써야 할까?

초 단위 시계 표시, 폴링 등 화면을 부드럽게 그릴 필요가 없는 반복 작업에는 여전히 적합하다. 하지만 캔버스·CSS 애니메이션처럼 눈에 보이는 움직임을 그릴 때는 rAF가 화면 주사율에 맞춰 부드럽고, 불필요한 렌더링(화면에 안 그려질 프레임)을 만들지 않아 항상 더 낫다.

💡 rAF 콜백 안에서 무거운 연산을 하면?

콜백이 16.7ms 예산을 넘기면 그 프레임은 이미 밀린 것이고, 브라우저는 다음 리페인트 때 다시 한 번만 호출한다 — 밀린 프레임을 몰아서 여러 번 호출해주지 않는다. 그래서 콜백 내부는 항상 가볍게 유지하고, 무거운 계산은 Web Worker로 분리하는 것이 정석이다.