[Canvas-불꽃놀이-08] 애니메이션 기본 틀잡기

이전 글 : [Canvas-불꽃놀이-07] 불꽃놀이 Particle들의 부모 클래스

 

[Canvas-불꽃놀이-07] 불꽃놀이 Particle들의 부모 클래스

이전 글 : 2024.11.18 - [개발/Canvas] - [Canvas-불꽃놀이-06] TextData 클래스 [Canvas-불꽃놀이-06] TextData 클래스이전 글 : 2024.11.18 - [개발/Canvas] - [Canvas-불꽃놀이-05] 화면 전환, 입력 이벤트 정의 [Canvas-불

jinsk-joy.tistory.com

 

requestAnimationFrame 메서드로 애니메이션 구현하기

 

1. requestAnimationFrame() : MDN 문서

  • window객체의 메서드인 requestAnimationFrame() 은 브라우저가 애니메이션을 수행하기 위해 제공하는 API다.
  • requestAnimationFrame() 은 애니메이션이 진행중 다음 화면을 그리기 전에 개발자가 제공한 애니메이션 관련 콜백 함수를 실행하도록 브라우저에 요청하는 역할을 한다.
  • 콜백 함수는 일반적으로 사용자의 화면 주사율 (보통 60hz)에 맞춰 호출된다. 추가로 고사양 모니터에서는 75hz, 120hz, 144hz 또한 넓게 사용된다.
  • requestAnimationFrame() 에서 성능 최적화와 배터리 수명 향상을 위해 백그라운드 탭이나 숨겨진 <iframe>에서 실행이 일시적으로 중단된다.
  • requestAnimationFrame(callback) 을 호출하면 전달된 콜백함수에 DomHighResTimeStamp형태의 timestamp 가 인자로 전달된다. timestamp 는 프레임의 렌더링이 완료된 시점을 기준으로 하며 이를 이용해 프레임간 간격을 계산할 수 있다. 또한 여러 창과 <iframe>에서 동일한 값을 공유할 수 있어 애니메이션 동기화에 유용하다.
  • 애니메이션을 취소하고 싶으면 requestAnimationFrame() 의 반환값인 request IDcancelAnimationFrame()에 전달하면 된다.
  • 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 링크
 

GitHub - jinsk9268/text-fireworks

Contribute to jinsk9268/text-fireworks development by creating an account on GitHub.

github.com