애니메이션을 구현하는 방법에는 어떤 것들이 있을까요 ?!
css
를 이용하여 구현한다.
requestAnimationFrame
전역 메소드를 활용하여 구현한다.
setTimeout
, setInterval
메소드를 활용하여 구현한다.
우선 이번 어플리케이션은 프로그램 명세에 맞게 requestAnimationFrame
으로 애니메이션을 구현해보았습니다. 하지만 문득 이런 고민 들지않았나요?
다른 방식으로 짜면 구린건가..?
그래서 이번 절에선 세 개간 트레이드 오프를 간단하게나마 비교해보려고 합니다.
브라우저의 렌더링 - layout(reflow)
: 렌더 트리를 실제 뷰포트에 배치하는 과정
브라우저의 렌더링 - paint(repaint)
: 실제 브라우저 화면에 그리는 과정
브라우저의 렌더링 - composite
: 페인트 과정에서 분리하여 작업해놓았던 레이어들을 합성하는 과정
event loop
가 관리하는 자료구조와 동작방식
CSS 기반 애니메이션과 기본 지원되는 웹 애니메이션은 일반적으로
컴포지터 스레드
라고 불리는 스레드에서 처리됩니다. 이것은 스타일 지정, 레이아웃, 페인트 및 자바스크립트가 실행되는 브라우저의메인 스레드
와는 다릅니다.
메인스레드의 작업이 커진다면, JS Animation
은 밀리게 되겠죠 ?! 하지만 CSS Animation
이라면 별개의 스레드에서 실행되므로 밀리지 않게 될것입니다.
애니메이션이 페인트 및/또는 레이아웃을 트리거하는 경우, 작업을 수행하기 위해
메인 스레드
가 필요합니다. 이는 CSS 및 자바스크립트 기반 애니메이션에 모두 적용되며, 레이아웃 또는 페인트의 오버헤드는 CSS 또는 자바스크립트 실행과 연관된 모든 작업에 악영향을 미치고, 해결 불가능한 문제를 유발할 수 있습니다.
보통 많이 들었을테지만, reflow
와 repaint
의 비용은 굉장히 크다고 소문이 나있죠. 이를 트리거 하는 애니메이션 속성들이 있을텐데 이를 피하는 것도 좋겠네요. 그게 뭔지 궁금하다면?
그래서 일단 CSS Animation
의 장점은 제가 생각하기엔
개발에 걸리는 시간과 노력이 비교적 덜 들어간다. (덜 복잡하다)
관심사가 분리되어 유지보수에 편할것 같아요.
메인스레드와 분리된 스레드에서 동작하기 때문에, 수행 시간을 보장받을 수 있을 것 같아요.
단점은 이럴거 같아요.
CSS Anmation
과 비교하여 생각해본다면 다음과 같은 문제점이 있을 것 같아요
관심사가 분리되지 않아 유지보수가 힘들다.
메인스레드에서 동작하기 때문에 수행 시간을 보장받지 못할 수 있다.
그렇기 때문에 모든 애니메이션을 JS로 구현하면 좋지 않을 것 같아요.
하지만 애니메이션을 구현할 때, 애니메이션 도중에 세밀한 제어가 필요한 경우라면 이 메소드를 활용해볼 수 있을 것 같아요.
requestAnimationFrame
vs setTimeout
으로 애니메이션 구현하기결론부터 말씀드리자면, rAF
로 애니메이션을 구현하는 경우 그 애니메이션은 부드럽습니다. 하지만 setTimeout
으로 구현하는 경우 애니메이션은 부드럽지 않습니다. 왜 일까요?
박스가 돌아가는 애니메이션을 rAF
를 함수로 구현해보겠습니다.
const box = document.querySelector(".box");
const rotateAnimation = (progress, start, node, during) => {
if (progress >= during) {
return;
}
node.style.transform = `rotate(${progress / 10}deg)`;
requestAnimationFrame((timestamp) =>
rotateAnimation(timestamp - start, start, node, during)
);
};
requestAnimationFrame((timestamp) => rotateAnimation(0, timestamp, box, 5000));
함수가 어떤 명세를 따르는지 궁금하다면 ? 요기를 찾아보세요
박스가 돌아가는 애니메이션을 setTimeout
으로 구현해보겠습니다. 재귀 방식을 사용하여 애니메이션을 구현해보았습니다.
const box = document.querySelector(".box");
function setTimer(timestamp, start) {
const progress = timestamp - start;
setTimeout(() => {
box.style.transform = `rotate(${progress / 10}deg)`;
setTimer(new Date().getTime(), start);
}, 16);
}
setTimer(new Date().getTime(), 0);
구현은 위와 같이 할 수 있어요. 지금은 당연히 두 방식 다 부드럽게 애니메이션이 보일거에요. 그런데 왜 rAF Animation
는 부드럽고, setTimeout Animation
은 부드럽지 않다고 하는 걸까요?
task queue
에 애니메이션 콜백만 있을까요?1프레임 당 한번의 호출을 보장하는가
모니터 주사율이 60hz의 경우 화면 갱신 속도는 60fps이고, 이 때 1프레임은 16ms 정도 나와야한다.
rAF
의 경우 1프레임당 호출이 보장됩니다. 이 때 렌더링 파이프라인에 진입했을 때 부터 다음 파이프라인에 진입하는 그 사이를 1프레임이라하구요.
rAF는 Animation frames라는 브라우저 렌더링과 관련된 task를 별도로 처리하는 queue를 통해 실행시점을 보증할 수 있습니다.
setTimeout
의 경우 이벤트 루프가 관리하는 taskqueue
내부의 다른 task에 의해 지연될 수 있어 끊길 수 있을 것 같아요
setTimeout은 task queue에 올라가 동작하고, rAF는 렌더링 파이프라인과 붙어 동작하는 것을 확인할 수 있다. 이런 구조 상 rAF는 무조건 1프레임 당 1번의 호출이 보장되고 setTimeout은 지연되어 2프레임 당 1번, 또는 3프레임 당 1번 호출될 가능성도 있다.
정리하자면, 단순한 애니메이션의 경우 (one-shot
이자, 세부 제어가 필요없는 경우)라면 CSS Animation
으로 작성하면 될 것 같아요. 그 외 복잡한 애니메이션이라면 JS Animation
이 방안이 될 수 있을것이고, requestAnimationFrame
메소드를 통해 확실한 애니메이션을 표현해내면 되겠군요!
정리 너무 깔끔하네요~ 많은 도움이 되었어요 :)