Language/Javascript

Javascript - 애니메이션 구현 setInterval vs requestAnimationFrame

둉이 2021. 12. 12. 00:03

보통 자바스크립트로 애니메이션을 구현해야 할 경우, setInterval과 requestAnimationFrame을 이용한 방식이 가장 많이 사용된다.

 

나는 setInterval을 주로 사용했었는데, setInterval로 만든 애니메이션의 경우에는 같은 애니메이션을 동시에 여러 번 호출하는 경우 애니메이션이 꼬이면서 버벅임이 발생할 수 있다는 단점이 있다.

(애니메이션 함수가 종료되기 전에 이벤트를 실행하는 경우에는 중첩돼서 이벤트가 실행되기 때문에 clearInterval로 기존 이벤트를 지워줘야 함)

 

그래서 이번에 requestAnimationFrame으로 코드를 리팩토링하면서 알게 된 setInterval과 requestAnimationFrame의 차이점을 간략히 정리했다.

 

setInterval

이벤트 지정

첫 번째 파라미터로 callback 함수, 두 번째 파라미터로 실행 간격(default = 0)을 전달한다. 1초의 경우, 1000을 전달하면 된다.

주의할 점은 callback 함수 내에 종료 조건을 명시하지 않으면 setInterval은 무한히 실행된다.

let t = 10;
let animation = null;
const callback = () => {
  if (--t <= 0) clearInterval(animation);
  console.log('callback', t);
}

animation = setInterval(callback, 1000);

 

이벤트 취소

clearInterval에 setInterval 함수를 담고 있는 변수를 전달한다.

clearInterval(animation);

 

사용 예시

donut.forEach((e, i) => {
  let t = 0;
  let w = Math.round(deptList[i].percent * 100);
  let width = w + (i === idx2 ? 0 : Math.round(deptList[i].percent2 * 100));

  const color = graphColor[i === idx2 ? 1 : 0];
  const handleDonutAnimation = () => {
    e.querySelector('.donut').style.background =
      t <= width && i === idx2
      ? `conic-gradient(${color} 0% ${t}%, ${graphColor[3]} ${t}% 100%)`
      : `conic-gradient(${color} 0% ${Math.min(t, w)}%, ${graphColor[2]} ${Math.min(t, w)}% ${Math.min(
      t,
      width,
      )}%, ${graphColor[3]} ${Math.min(t, width)}% 100%)`;
    t++ === width && clearInterval(donutAnimation);
  };
  const donutAnimation = setInterval(handleDonutAnimation, 10);
});

 

requestAnimationFrame

이벤트 지정

requestAnimationFrame 함수에 callback을 전달한다. setInterval 함수를 사용하여 애니메이션을 구현할 때와는 달리 callback 함수 내에서 requestAnimationFrame을 재호출해야 한다.

let t = 60;

const callback = () => {
   console.log('callback', t--);
   t && requestAnimationFrame(callback);
}

requestAnimationFrame(callback);

 

이벤트 취소

이벤트를 실행했을 때와 마찬가지로 callback 함수를 cancelAnimationFrame 함수에 전달하면 현재 실행중인 애니메이션을 취소할 수 있다.

cancelAnimationFrame(callback);

 

타임스탬프 사용

requestAnimationFrame 함수의 callback 함수의 매개변수로 timestamp 변수를 전달하면 requestAnimationFrame 함수가 실행되기 시작한 이후의 시간을 받을 수 있다.

let startTime = null;

function draw(timestamp) {
    if(!startTime) {
      startTime = timestamp;
    }

   currentTime = timestamp - startTime;

   // Do something based on current time

   requestAnimationFrame(draw);
}

draw();

 

사용 예시

donut.forEach((e, i) => {
  let t = 0;
  let w = Math.round(deptList[i].percent * 100);
  let width = w + (i === idx2 ? 0 : Math.round(deptList[i].percent2 * 100));

  const color = graphColor[i === idx2 ? 1 : 0];
  const handleDonutAnimation = () => {
    e.querySelector('.donut').style.background =
      t <= width && i === idx2
      ? `conic-gradient(${color} 0% ${t}%, ${graphColor[3]} ${t}% 100%)`
      : `conic-gradient(${color} 0% ${Math.min(t, w)}%, ${graphColor[2]} ${Math.min(t, w)}% ${Math.min(
      t,
      width,
      )}%, ${graphColor[3]} ${Math.min(t, width)}% 100%)`;
    t++ !== width && requestAnimationFrame(handleDonutAnimation);
  };
  requestAnimationFrame(handleDonutAnimation);
});

 

setInterval과 requestAnimationFrame의 차이점

둘의 차이점은 다음과 같다.

 

1. 속도 조절 가능 여부

setInterval은 속도를 조절할 수 있는 반면, requestAnimationFrame은 속도 조절이 불가하다.

무조건 1초에 60번 실행된다고 한다. (0.0167초에 한 번 실행)

그 이유는 일반적인 모니터의 화면 재생률이 60Hz이기 때문이다.

 

2. 콜백 실행 방식

requestAnimationFrame에서는 다음 콜백을 실행하려면 반드시 콜백 함수 내에서 requestAnimationFrame을 재호출해야만 한다.

 

반면에 setInterval의 경우에는 clearInterval로 중지하지 않는 한 영원히 실행된다. 만약 애니메이션을 중지하고 싶다면, 해당 함수를 갖는 변수를 별도로 선언한 후, clearInterval 함수에 해당 변수를 파라미터로 전달하여 중지시켜야 한다.

 

만약 setInterval로 만든 애니메이션을 중지하지 않고 또 애니메이션 함수를 호출하는 경우에는 아래와 같이 애니메이션이 겹쳐져서 버벅임이 발생한다.

 

버벅....버어억...
정상

 

3. 프레임의 부드러움

setInterval으로 구현한 애니메이션은 약간의 프레임 끊김이 발생하거나 프레임 자체를 빠뜨리는 문제가 발생할 수 있다.

 

requestAnimationFrame은 애니메이션을 위해 최적화된 함수이므로 애니메이션이 실행되는 환경에 관계 없이 적절한 프레임 속도로 실행되며, 탭이 활성화되지 않은 상태이거나 애니메이션이 페이지를 벗어난 경우에도 계속 실행되는 기존의 문제점을 해결할 수 있는 방법이라고 한다.

 

4. IE에서의 지원 여부

requestAnimationFrame은 IE 10버전부터 지원한다.

따라서 IE 10버전 이하 브라우저도 지원하려면 setInterval을 사용하거나 polyfill로 requestAnimationFrame을 주입해야 한다.