# requestAnimationFrame의 근본적인 목적
requestAnimationFrame(rAF)은 브라우저 렌더링 파이프라인을 모니터 주사율(VSync)과 동기화하기 위한 API이다. 가장 중요한 점은 애니메이션 문제의 출발점이 부라우저가 아니라 모니터의 물리적 갱신 주기라는것이다.
# 모니터의 물리적 특성
•60Hz : 16.67ms 마다 화면 갱신
•120Hz : 8.33ms 마다 화면 갱신
•144Hz : 6.94ms 마다 화면 갱신
•240Hz : 4.17ms 마다 화면 갱신
이는 하드웨어의 물리적 제약으로, 아무리 소프트웨어가 빠르게 동작해도 화면은 이 특정 시점에서만 실제로 갱신된다.
# 애니메이션의 본질: 프레임과 착시
우리가 보는 모든 애니메이션은 사실 정적인 이미지의 연속이다. 영화 필름 처럼 짧은 간격으로 조금씩 다른 정적 사진들을 빠르게 보여주면, 인산의 눈은 이것을 연속적인 움직임으로 인식한다. 이때 정적인 사진 한 장을 1프레임(frame)이라고 한다.
인간의 시각 시스템은 초당 약 60 프레임 이상을 봐야 끊김 없이 자연스러운 움직임으로 느낀다. 이보다 적으면 프레임 사이의 빈 공간을 인지하게 되어 뚝뚝 끊기는 느낌을 받는다.
따라서 웹에서 부드러운 애니메이션을 만드려면 최소 60 프레임(16.67ms 간격)으로 구현해야 사용자가 부드럽게 느끼게 된다. 이것이 바로 60fps(frame per second)가 웹 애니메이션의 기준이 되는 이유다
# 브라우저 애니메이션 구조
애니메이션을 만들고 제어하는 과정은 크게 두 단계로 나눌 수 있다.
# 애니메이션 구현 (CSS)
•실제 화면에서 요소가 움직이거나 변화하는 효과를 만드는 단계
•CSS Animation / transition 사용
•브라우저가 최적화 가능 → GPU compositing 단계만 사용 가능 (transform, opaciry)
•선언적 방식으로 최적화 + 성능 보장
# 애니메이션 제어 (JavaScript)
•애니메이션 값을 언제 얼마만큼 바꿀지 결정하는 단계
•JS에서 값을 직접 업데이트하는 방식은 크게 두가지
# Timer API (setTimeout, setInterval)
setTimeout/setInterval은 지정한 시간마다 JS 코드를 실행하는 방식이다.
•모니터 갱신 주기와 동기화되지 않아 실행 타이밍 불규칙
•JS 실행 → Layout → Paint → Composite까지 전체 렌더링 경로를 모두 거쳐야 함 → 리소스 낭비 발생
# ⭐️requestAnimationFrame
requestAnimationFrame은 모니터의 물리적 화면 갱신 주기와 Javascript 애니메이션 코드를 완벽하게 동기화하기 위해 만들어진 Web API이다.
모니터 주사율에 따라 60Hz는 16.67ms마다, 144Hz는 6.94ms마다 정확히 화면을 갱신한다. setTimeout은 이 주기를 전혀 모르고 임의의 시점에서 실행되어 모니터가 준비되지 않은 상태에서 DOM을 변경하거나 한 갱신 주기 내에 여러번 변경하여 중간 프레임을 손실시킨다. rAF는 브라우저가 지금 다음 화면을 그릴 준비가 되었다고 판단한 바로 그 시점 렌더링 파이프라인이 시작되기 직전에 콜백을 실행하여 모든 DOM 변경사항이 확실하게 화면에 반영되도록 보장한다.
# rAF 동작방식과 타이밍
사용자 인터렉션 이벤트가 최우선으로 처리된 후 Timer API(setTimeout, setInterval) 그 다음 rAF가 모니터 주사율에 맞춰서 가장 마지막에 실행된 후 브라우저 렌더링 6단계가 실행된다.
결국 rAF의 핵심은 애니메이션 타이밍을 부라우저와 모니터의 실제 렌더링 순간에 정확히 맞춘다는 점이다. 이는 개발자가 별도의 시간 계산 없이도 안정적인 60fps 애니메이션을 구현할 수 있게 해주며 불필요한 중간 프레임 생성이나 레이아웃 재계산을 최소화해 성능을 극대화한다.
# Timer vs rAF 코드 비교
목표:
•화면 왼쪽에서 오른쪽으로 1초 동안 이동
•이동 값은 JS로 매 프레임 계산
•Timer와 rAF 사용 시 프레임 동기화, 중복 렌더링, 끊김 현상 차이 비교
# Timer 기반
const box = document.querySelector('.box');
let x = 0;
const fps = 60; // 모니터 주사율
const step = 5; // 1 프레임 당 이동 픽셀 수
const timer = setInterval(() => {
x += step;
box.style.transform = `translateX(${x}px)`;
if(x > window.innerWidth) clearInterval(timer);
}, 1000 / fps)
특징
•지정된 간격(16.67ms)마다 Timer JS 실행
•브라우저 프레임과 동기화 되지 않음 → 실제 화면 갱신과 타이밍 불일치
•한 프레임 안에서 DOM 수정이 여러번 겹치면 중복 레이아웃/페인트 발생 → CPU/GPU 낭비
•고주사율 모니터 (120Hz, 144Hz)에서는 불연속 프레임 발생 가능
# requestAnimationFrame 기반
const box = document.querySelector('.box');
let x = 0;
const step = 5;
const animation = () => {
x += step;
box.style.transform = `translateX(${x}px)`;
if(x < window.innerWidth) requestAnimationFrame(animation)
}
requestAnimationFrame(animation)
특징
•브라우저가 다음 화면을 그릴 준비 완료 직전 콜백 실행 → 프레임 손실 없음
•브라우저가 60Hz든 144Hz든 자동 동기화 항상 매 프레임 자연스러운 움직임
•JS 계산량은 그대로지만, 렌더링 효율 극대화
# 결론
Timer는 JS 실행과 브라우저 렌더링이 동기화되지 않아, 한 프레임 안에서도 중복 Layout/Paint가 발생하고 프레임 손실이 생길 수 있다. requestAnimationFrame(rAF)은 브라우저 프레임 준비 시점에서만 JS를 실행하여 한 프레임에 한 번만 렌더링하고 중복 Layout/Paint를 제거한다. 따라서 부드러운 애니메이션과 고주사율 대응이 필요하면 rAF 사용이 효울적이다.
# 마무리
지금까지 requestAnimationFrame(rAF)가 모니터 주사율과 브라우저 렌더링 파이프라인에 맞춰 동작하도록 설계된 Web API라는 점과 Timer와의 차이, 실제 애니메이션 구현에서의 효율성을 살펴보았다.
하지만 최신 웹 개발에서는 rAF를 직접 호출하는 경우가 많지 않다. rAF는 명령형 API이기 때문에 개발자가 브라우저 최적화를 직접 고려해야 하고, 코드 유지보수가 어려울 수 있다. 따라서 GSAP, Framer Motion, Motion One 같은 애니메이션 라이브러리를 활용하거나, CSS/상태 기반 선언적 방법을 사용하는 것이 실무에서 더 일반적이며, 극한 퍼포먼스가 필요한 경우를 제외하면 직접 rAF를 사용할 필요가 거의 없다.