이전 글 : [Canvas-불꽃놀이-07] 불꽃놀이 Particle들의 부모 클래스
requestAnimationFrame 메서드로 애니메이션 구현하기
1. requestAnimationFrame() : MDN 문서
- window객체의 메서드인 requestAnimationFrame() 은 브라우저가 애니메이션을 수행하기 위해 제공하는 API다.
- requestAnimationFrame() 은 애니메이션이 진행중 다음 화면을 그리기 전에 개발자가 제공한 애니메이션 관련 콜백 함수를 실행하도록 브라우저에 요청하는 역할을 한다.
- 콜백 함수는 일반적으로 사용자의 화면 주사율 (보통 60hz)에 맞춰 호출된다. 추가로 고사양 모니터에서는 75hz, 120hz, 144hz 또한 넓게 사용된다.
- requestAnimationFrame() 에서 성능 최적화와 배터리 수명 향상을 위해 백그라운드 탭이나 숨겨진 <iframe>에서 실행이 일시적으로 중단된다.
- requestAnimationFrame(callback) 을 호출하면 전달된 콜백함수에 DomHighResTimeStamp형태의 timestamp 가 인자로 전달된다. timestamp 는 프레임의 렌더링이 완료된 시점을 기준으로 하며 이를 이용해 프레임간 간격을 계산할 수 있다. 또한 여러 창과 <iframe>에서 동일한 값을 공유할 수 있어 애니메이션 동기화에 유용하다.
- 애니메이션을 취소하고 싶으면 requestAnimationFrame() 의 반환값인 request ID를 cancelAnimationFrame()에 전달하면 된다.
- requestAnimationFrame() 은 한 번만 실행되므로, 애니메이션을 지속하려면 콜백 함수 내에서 재귀적으로 requestAnimationFrame() 을 다시 호출해야 한다.
// 선언
requestAnimationFrame(callback);
// 취소
const animationId = requestAnimationFrame(callback);
cancelAnimationFrame(animationId);
2. 애니메이션 적용
- 주사율이 높은 모니터에서도 60fps로 애니메이션 속도를 일정하게 유지하기 위해 다음과 같이 구현하였다.
- 계산을 위해 필요한 주요 변수
- now : 현재 프레임 시작 시간으로, requestAnimationFrame의 콜백 함수에 전달되는 DomHighResTimeStamp (밀리초 단위의 고해상도 시간값) 이다.
- then : 이전 프레임 종료 시간으로, 초기값은 document.timeline.currentTime으로 설정한다. 이는 requestAnimationFrame()의 timestamp와 동일한 기준을 가지므로 0프레임을 기준으로 애니메이션을 정확히 시작할 수 있게한다.
- delta : 현재 프레임과 이전 프레임 간의 실제 경과 시간이다.
- 작동 원리
- delta가 설정된 프레임 간격 (interval, 약 16.67ms)을 넘지 않으면 프레임을 업데이트 하지 않는다
- delta가 interval을 넘는 경우 애니메이션 요소를 업데이트하고 오차를 보정하여 다시 일정한 간격으로 렌더링 될 수 있게 한다.
- 오차 보정
- requestAnimationFrame의 호출 간격은 브라우저 성능이나 모니터 주사율에 따라 약간 변동될 수 있다.
- 따라서 delta 값이 interval과 일치하지 않은 경우가 발생한다. 이러한 오차가 계속 누적되면 애니메이션 속도가 불안해 질 수 있다.
- 이를 위해 now에서 delta를 interval로 나눈 나머지 값(오차값)을 제외해 보정된 시간을 계산해 then값을 보정한다.
- 이를 통해 누적된 오차를 제외하고 프레임을 일정한 간격으로 유지할 수 있다.
animateFireworks() {
// requestAnimationFrame과 동일한 시간 기준으로 0프레임부터 시작 가능
let then = document.timeline.currentTime;
/**
* requestAnimationFrame에 전달할 콜백 함수
* @param {number} now requestAnimationFrame의 timestamp (밀리 초)
*/
const frame = (now) => {
// 현재 프레임과 이전 프레임 간의 시간 차이
const delta = now - then;
// delta가 interval 이상인 경우 애니메이션 실행
if (delta >= this.interval) {
// 애니메이션 실행 코드 (구체적 작업)
// 오차 보정
// delta % this.interval: interval에서 초과된 시간 계산
// now - (delta % this.interval): 초과 시간을 제거하여 then 보정
// 이를 통해 누적된 시간 오차를 방지하고 일정한 프레임 간격(interval)을 유지
then = now - (delta % this.interval);
}
// 다음 프레임 요청,
// frame 함수 내부에서 requestAnimationFrame을 다시 호출하여
// 매 프레임마다 애니메이션을 지속적으로 갱신
this.animationId = requestAnimationFrame(frame);
};
// 첫번째 프레임 요청
this.animationId = requestAnimationFrame(frame);
}
3. 실제로 60프레임이 나오는지 확인해보기
- 실제로 60프레임이 나오는지 테스트 코드를 추가해서 확인해보았다.
animateFireworks() {
let then = document.timeline.currentTime;
// 시간 측정
let frameCount = 0;
let frame = 0;
let lastTime = document.timeline.currentTime;
const frame = (now) => {
const delta = now - then;
if (delta >= this.interval) {
then = now - (delta % this.interval);
// 시간 측정
frameCount++;
if (now - lastTime >= 1000) {
frame = frameCount;
console.log("fps -> ", frame);
frameCount = 0;
}
}
this.animationId = requestAnimationFrame(frame);
};
this.animationId = requestAnimationFrame(frame);
}
Github 링크
'Canvas > 불꽃놀이 프로젝트' 카테고리의 다른 글
[Canvas-불꽃놀이-10] 불꽃놀이 꼬리의 위치와 속도 구현 (0) | 2024.11.22 |
---|---|
[Canvas-불꽃놀이-09] 파티클 관리와 객체 폴링 (1) | 2024.11.22 |
[Canvas-불꽃놀이-07] 불꽃놀이 Particle들의 부모 클래스 (1) | 2024.11.18 |
[Canvas-불꽃놀이-06] TextData 클래스 (2) | 2024.11.18 |
[Canvas-불꽃놀이-05] index.js에서 중요 이벤트 정의 (1) | 2024.11.18 |