requestAnimationFrame과 cancelAnimationFrame의 관계

QA팀으로부터 현재 운영 중인 웹사이트 관련 리포트를 몇 건 전달받았습니다. 사이트를 탐색하다 보면 전체적으로 느려진다는 내용이었는데, 리포트들의 공통점은 특정 애니메이션을 실행한 이후부터 사이트가 느려진다는 점이었습니다.

해당 애니메이션은 loop되는 구조로, 특정 영역에 mouseEnter 하면 재생되었다가 mouseLeave 하면 애니메이션이 숨김 처리되는 형식이었습니다. 애니메이션을 재생한 상태에서 개발자 도구로 성능/메모리 상태를 살펴보니 Animation Frame Fired 항목이 가장 높은 점유율을 차지했고, 애니메이션이 화면에서 사라졌음에도 지속적으로 리소스를 점유한다는 사실을 확인했습니다.

커버 이미지

그래서 애니메이션 최적화 방법을 찾아보니 requestAnimationFrame이 계속 언급되었고, 마침 운영 중인 사이트에서도 이를 사용하고 있길래 관심을 갖게 되었습니다. 그렇다면 requestAnimationFrame이 무엇이며, 이를 어떻게 "정상적으로 종료"해야 할까요?

requestAnimationFrame이란?

JavaScript의 내장 함수로, 다음 프레임에 실행할 애니메이션 콜백을 등록하고 브라우저가 적절한 시점에 호출하도록 하는 API입니다.

기존 setInterval과 달리 디스플레이 주사율에 맞춰 호출 시점이 조절되고, 백그라운드 상태에서는 호출이 제한(또는 중단)된다는 장점이 있습니다.

다만 유의해야 할 점이 있습니다. setIntervalclearInterval로 타이머를 해제하듯이, requestAnimationFrame도 종료 조건이 없다면 cancelAnimationFrame으로 루프를 정리해주셔야 합니다.

 
// 애니메이션 id를 저장할 변수
let id = 0;
 
 
...
function init(){
	...
    console.log("animation start!!");
    animate();
}
 
...
// 매 프레임마다 id를 갱신한다.
function animate() {
	console.log("animate...");
	id = requestAnimationFrame(animate);
}
 
...
// 애니메이션 종료 시점에 호출하여 저장해둔 id를 인자로 전달합니다.
function destroy(){
	console.log("animation end // animation id : ", id);
  	cancelAnimationFrame(id);
}
 

사용법은 간단합니다. requestAnimationFrame은 id를 반환하므로, 해당 id를 저장해두었다가 애니메이션을 종료해야 하는 시점에 cancelAnimationFrame(id)를 호출하면 됩니다.

cancelAnimationFrame 적용 비교

cancelAnimationFrame이 잘 동작하는지 확인하기 위해 간단한 테스트 코드를 만들어 보았습니다. 영역을 하나 만들고 mouseEnter 하면 애니메이션이 재생되고, mouseLeave 하면 애니메이션이 사라지면서 멈추는 형식입니다. 해당 영역에 마우스를 처음 10초 동안 mouseEnter 상태로 두고, 다음 10초는 mouseLeave 상태로 두는 방식으로 테스트했습니다.

cancelAnimationFrame 함수 적용 전

이미지1

이미지2

cancelAnimationFrame 함수 적용 후

이미지3

이미지4

성능 수치 비교

위가 cancelAnimationFrame 적용 전이고, 아래가 적용 후입니다. mouseEnter 10초, mouseLeave 10초로 동일한 조건에서 DevTools Performance 탭으로 측정한 결과를 비교하면 차이가 확실합니다.

측정 항목적용 전적용 후변화
Animation Frame Fired CPU 점유율 (mouseLeave 후)41%3%93% 감소
초당 animate 함수 호출 횟수 (mouseLeave 후)~60회/초 (60fps 기준)0회/초완전 중단
전체 페이지 Scripting 시간 (20초 기준)8.2초4.1초50% 감소
메모리 사용량 증가 추이 (mouseLeave 후 10초간)지속 증가 (~2~3MB)증가 없음 (안정)메모리 누수 해소

문제의 핵심은 mouseLeave 이후에도 requestAnimationFrame 루프가 멈추지 않는다는 것이었습니다. 적용 전 그래프를 보면 애니메이션이 화면에서 사라진 뒤에도 Animation Frame Fired가 CPU의 41%를 점유하고 있었고, 콘솔에서도 animate 함수가 초당 60회씩 계속 호출되고 있었습니다. 보이지 않는 애니메이션이 백그라운드에서 리소스를 갉아먹고 있었던 셈입니다.

cancelAnimationFrame을 적용한 뒤에는 mouseLeave 시점에 루프가 즉시 끊기면서 CPU 점유율이 3%로 떨어졌고, animate 호출도 완전히 멈췄습니다. 메모리 쪽도 마찬가지입니다. 적용 전에는 콜백 참조가 해제되지 않아 10초간 2~3MB씩 증가하던 것이, 적용 후에는 안정적으로 유지되었습니다.

이 결과를 참고해 실제 웹사이트에서도 cancelAnimationFrame을 적용하니, 애니메이션이 사라진 후에도 계속 리소스를 점유하던 현상이 사라졌고 사이트가 느려지는 문제도 해결되었습니다.

샘플 코드

github 저장소 이동

참고 자료