1. requestAnimationFrame (RAF)란?
RequestAnimationFrame 은 브라우저 내장 API이다.
브라우저 렌더링 주기(보통 60FPS)에 맞춰 콜백을 실행한다.
애니메이션을 부드럽게 실행하도록 도와주고, 프레임을 브라우저가 최적화하여 처리한다.
때문에 애니메이션 구현시, setInterval 대신 requestAnimationFrame을 더 많이 활용한다.
2. requestAnimationFrame 동작원리
애니메이션을 부드럽게 실행하기 위해
브라우저는 다음 렌더링 시점에 애니메이션을 예약하고 '매 프레임마다 자동으로 호출'한다.
브라우저의 리페인트 주기(60FPS) 에 맞춰 실행되는 것이다.
즉, 브라우저가 1초당 출력할 수 있는 프레임 수(fps) 기준으로 requestAnimationFrame(callback)을 사용하면,
그 프레임 간격마다 애니메이션이 반복 실행된다.
덕분에 애니메이션의 타이밍이 브라우저의 화면 갱신 주기와 정확히 일치하게 된다.
※ Frame 이란?
우리가 보는 영상이나 애니메이션은 정지된 이미지가 연속적으로 빠르게 바뀌는 것이다.
이 낱장의 정지된 이미지 하나를 프레임(Frame) 이라고 한다.
※ fps란? (Frames Per Second)
초당 프레임 수를 의미하며, 60fps는 1초 동안 60장의 화면을 그린다는 뜻이다.
일반적인 브라우저는 60FPS(1초당 60번 갱신) 을 목표로 한다.
1프레임 간격은 약 16.67ms(1초 ÷ 60) 이다.
3. requestAnimationFrame (RAF)와 태스크 큐
자바스크립트는 싱글스레드인로 동작하지만, 비동기 실행을 위해 이벤트 루프가 브라우저에서 실행된다.
비동기 실행을 위한 콜백함수들은 태스크 큐에 대기하며,
이후 콜스택에 아무런 태스크가 없으면 이벤트 루프에 의해 태스트 큐에 등록된 작업을 하나씩 가져와 나중에 실행하게 된다.
태스크 큐엔 1.마이크로 태스크 큐 2.매크로 태스크 큐가 있다.
그런데 requestAnimationFrame가 이 두 태스크 큐 사이에 위치한다는 것을 알게되었다.
✅ 태스크 큐의 작업 순서
1. 마이크로 태스트 큐 : Promise 후속처리 메서드 콜백함수
2. requestAnimationFrame : requestAnimationFrame 처럼 브라우저 렌더링 관련된 콜백함수
3. 매크로 태스크 큐 : setTimeout 등 그 외 나머지 비동기 콜백함수
console.log("Start"); // 1
// 마이크로 태스크: Promise
Promise.resolve().then(() => {
console.log("Microtask 1: Promise then"); // 3
}).then(() => {
console.log("Microtask 2: Chained Promise then"); // 4
});
// 애니메이션 프레임: requestAnimationFrame
requestAnimationFrame(() => {
console.log("Animation Frame: requestAnimationFrame callback"); // 5
});
// 매크로 태스크: setTimeout
setTimeout(() => {
console.log("Macrotask: setTimeout callback"); // 6
}, 0);
console.log("End"); // 2
4. requestAnimationFrame 사용
requestAnimationFrame(callback);
- callback : 다음 프레임이 렌더링될 때 실행할 함수. 리페인트 될 때 호출된다.
- 반환값 : 고유한 id를 반환한다.
이 값을 window.cancelAnimationFrame(id) 에 전달해 애니메이션을 취소할 수 있다.
✅ 사용코드 예제
requestAnimationFrame()은 한 번만 실행되므로 반복하려면 재귀적으로 호출해야 한다.
let rafY = 0;
let rafId
// 일정한 간격으로 애니메이션 발생
function animateRAF(timestamp) {
rafY += 2;
rafBox.style.transform = `translateY(${rafY}px)`;
rafId = requestAnimationFrame(animateRAF);
}
document.getElementById('start').addEventListener('click', () => {
rafId = requestAnimationFrame(animateRAF); // 애니메이션 발생
});
document.getElementById('stop').addEventListener('click', () => {
cancelAnimationFrame(rafId); // 애니메이션 취소
});
✅ 활용
CSS 애니메이션 대신 JavaScript 으로 애니메이션 구현시
- Canvas 에서 애니메이션 구현시 자주쓰인다. (ex. Canvas , WebGL 렌더링)
- 부드러운 스크롤 효과 (ex. Parallax, Scroll Animation)
5. 왜 setInterval 대신 requestAnimationFrame을 써야 하나? (RAF 특징)
✅ 실행 주기
1. setInterval은
고정된 시간 간격(setInterval(()⇒{}, 16ms))으로 코드를 실행하게 된다.
2. requestAnimationFrame은
브라우저의 화면 프레임 주기(일반적으로 60FPS, 약 16.67ms)에 맞춰 실행된다. 브라우저의 리프레시 속도에 맞춰 실행되는 것이다.
→ 이로 인해 프레임 손실 없이 더 부드러운 애니메이션을 만들 수 있다.
✅ 리소스 효율성
1. setInterval은 불필요한 CPU 사용을 초래할 수 있다.
탭이 비활성화되거나, 창이 최소화 되었을 때도 계속 실행된다.
이렇게 백그라운드에서도 계속 실행되므로 특히 모바일에서의 성능 저하 문제가 있다.
2. requestAnimationFrame은 비활성화된 상태에서는 자동으로 멈추게된다.
→ 불필요한 연산을 줄여 CPU 사용을 절약할 수 있다. (리소스 낭비 방지)
✅ 타이밍 정확도
1. setInterval은 타이밍이 정확하지 않다.
setInterval(()⇒{} , 16ms) 는 사실 고정된 시간 간격(16ms)로 정확히 실행되지 않는다.
왜냐면, 자바스크립트는 싱글 스레드 기반이기 때문에 동기 작업이 다 끝나야 비동기 코드가 실행된다.
이때 CPU사용량이 많아지거나, 작업에 부하가 있다면 setInterval의 콜백은 16ms보다 더 대기하게 되서 늦게 실행될 수 있기 때문이다.
→ 때문에 프레임 간 간격이 불규칙해져 애니메이션이 끊기는 현상이 발생한다!
2. requestAnimationFrame은 브라우저 렌더링 사이클과 정확히 동기화되서 프레임 간 일관성이 유지된다.
- 브라우저 리페인트 주기(60FPS, 약16.67ms)마다 requestAnimationFrame의 callback이 자동으로 실행된다.
- CPU가 부하가 많더라도, 브라우저가 알아서 조정해서 다음 프레임을 실행할 타이밍을 맞추게된다.
→ 때문에 프레임 간 간격이 균일해서 애니메이션이 부드럽게 실행된다!
6. 구현예제 - setInterval VS requestAnimationFrame
실제로 구현을 해보면 그 차이를 느낄 수 있다.
requestAnimationFrame은 일정한 속도로 부드럽게 이동하지만, setInterval은 확연히 끊기는 것을 볼 수 있다.
지금은 간단한 예시지만, 좀 더 복잡한 인터렉션이 발생하고 CPU에 부하가 높아지면 setInterval 은 확연히 더 느려질 것이다.
'프론트엔드' 카테고리의 다른 글
Webpack 설정으로 알게된 publicPath, historyApiFallback (0) | 2025.03.16 |
---|---|
React-Query 사용기 (0) | 2024.10.20 |
FTP와 SFTP에 대해, RSA에 대해 (0) | 2024.10.04 |
css scope, scope 무시하기 (0) | 2024.09.22 |
Axios interceptor (1) | 2024.08.10 |