일반적으로 웹에서 애니메이션 작업을 위해서는 css를 사용하는데 주로 transform, translate, animation 속성을 이용한다. 하지만 css로는 구현하기 어려운 애니메이션 작업을 구현하기 위해서는 자바스크립트를 사용하기도 하는데 이 때 자연스러운 애니메이션을 구현하기 위해서 requestAnimationFrame이 사용된다고 한다. 브라우저의 렌더링 과정도 소개하면서 함께 requestAnimationFrame에 대해서 알아보도록하자.
브라우저 렌더링 과정
위의 사진을 글로 간단하게 요약해보자면 브라우저의 렌더링 엔진은 서버로부터 받은 html과 css를 통해 각각 DOM과 CSSOM을 생성하고, 생성된 DOM과 CSSOM을 결합하여 렌더 트리를 생성한다. 생성된 렌더 트리를 기반으로 레이아웃을 계산하고, 계산된 레이아웃을 기반으로 실제로 화면에 그리는 페인트 과정을 거친다. 브라우저는 화면에 출력되는 객체들을 합성하여 최종적인 화면을 생성한다.
추가적으로 html 파싱의 결과로 생성된 DOM은 자바스크립트에서 DOM API를 사용하여 동적으로 조작하는 것이 가능하다. 따라서 실행된 자바스크립트 코드에 DOM, CSSOM을 변경하는 DOM API가 사용된 경우 변경된 DOM, CSSOM은 다시 결합되어 렌더 트리가 생성되고 변경된 렌더 트리를 기반으로 리플로우와 리페인트 과정을 거치게된다.
브라우저 프레임 원리
모니터 주사율이라는 단어를 한 번 씩은 들어봤을것이다. 우리가 주변에서 흔하게 볼 수 있는 60Hz, 144Hz 모니터 주사율은 각각 초당 최대 60개의 프레임을 생성할 수 있고, 144Hz의 모니터는 초당 최대 144개의 프레임을 생성할 수 있다는 의미이다. 일반적으로 사용자가 버벅이지 않고 부드러운 애니메이션을 보기위해서는 1초에 60번 화면이 다시 그려져야하고, 따라서 하나의 프레임이 16.67ms (1000ms / 60fps) 간격으로 생성되어야한다. 이를 통해 브라우저의 렌더링 엔진은 모니터의 주사율을 기반으로 주기적인 리페인트 과정을 거쳐 하나의 프레임을 생성하기 위해 노력한다. 따라서 자바스크립트를 통해 애니메이션을 구현하는 경우 16.67ms 마다 호출되는 코드를 작성해야한다.
타이머 함수를 이용한 애니메이션 구현
브라우저 프레임 원리를 통해 알아본 바로는 자바스크립트를 통해 애니메이션을 구현하기 위해서는 60Hz 모니터 기준 16.67ms 마다 반복 호출되는 코드를 작성해야한다. 따라서 일정 시간마다 반복적으로 코드를 실행하는 타이머 함수인 setTimeout과 setInterval을 사용하여 다음과 같이 구현할 수 있다.
🔘 setTimeout
function timeoutRender() {
// 애니메이션 작업
const id = setTimeout(timeoutRender, 1000 / 60);
if () {
// setTimeout 중단 조건
clearTimeout(id);
return;
}
}
setTimeout(timeoutRender, 1000 / 60)
🔘 setInterval
function intervalRender() {
const id = setInterval(() => {
// 애니메이션 작업
if () {
// setInterval 중단 조건
clearInterval(id)
return
}
}, 1000 / 60);
}
intervalRender()
타이머 함수의 문제점
위와 같이 타이머 함수인 setTimeout과 setInterval을 사용하여 16.67ms 마다 반복적으로 코드를 실행하도록 구현했다. 하지만 setTimeout과 setInterval은 말그대로 주어진 시간내에서만 동작하기 때문에 프레임을 신경 쓰지 않고 동작한다. 브라우저 프레임 원리에서 알아본 내용으로는 브라우저가 주사율을 기반으로 주기적으로 리페인트 주기를 갖는다고 했다. 하지만 자바스크립트는 싱글 스레드이기 때문에 타이머 함수의 실행이 리페인트 주기에 실행됨을 보장하지 못한다.
다음 그림을 보자.
타이머 함수는 프레임을 신경 쓰지 않고 동작하기 때문에 위와 같이 브라우저의 다른 작업 수행으로 인해 타이머 함수의 콜백 함수가 16.67ms 의 중간에서 실행될 수 있다. 즉, 요소의 스타일 변경이 발생하는 타이머 함수의 콜백함수 실행이 모니터 주사율을 기반으로 발생하는 브라우저의 리페인트 주기와 일치하지 않을 수 있다. 따라서 해당 프레임이 생성되지 못하고 프레임 드랍이 발생하여 화면이 버벅이는 현상이 발생 할 수있다.
requestAnimationFrame
requestAnimationFrame은 앞서 설명한 타이머 함수의 단점을 보완한다. 브라우저의 렌더링 엔진이 모니터 주사율을 기반으로 리페인트 주기를 갖는 최적의 시점에 먼저 실행되도록 보장해준다. 즉, 프레임 시작 시에 실행되도록 보장해주기 때문에 타이머 함수와 다르게 프레임 드랍이 일어나는 것을 방지하여 보다 부드러운 애니메이션을 제공한다.
requestAnimationFrame은 다음과 같이 사용한다.
let rafId;
function rafRender() {
// 애니메이션 작업
if () {
// requestAnimationFrame 중단 조건
cancelAnimationFrame(rafId);
return;
}
rafId = requestAnimationFrame(rafRender);
}
requestAnimationFrame(rafRender)
추가적인 장점
requestAnimationFrame은 추가적인 장점 또한 존재한다.
🔘 백그라운드에서 일시 중지: setInterval과 setTimout과 같은 타이머 함수는 브라우저의 다른 탭을 보거나 브라우저가 최소화되어 있을 때와 같이 브라우저가 비활성화인 상태에도 반복적으로 실행되기 때문에 시스템 리소스를 낭비하지만 requestAnimationFrame은 화면의 리페인트 주기에 동기화되어 실행되기 때문에 브라우저가 비활성화인 상태에서는 일시중지 되어 리소스 낭비를 줄일 수 있다.
🔘 모니터 주사율을 기반으로 호출: requestAnimationFrame은 모니터 주사율을 따른다. 따라서 144Hz의 모니터에서 브라우저의 렌더링 엔진은 1초에 최대 144번 리페인트 과정을 가지는데 requestAnimationFrame은 해당 리페인트 주기와 동기화되기 때문에 requestAnimationFrame 역시 1초에 최대 144번 호출되게 된다. 따라서 호출 간격 시간을 직접 지정해야하는 타이머 함수와는 다르게 모니터의 주사율을 그대로 따르도록 최적화 되어있다.
setInterval vs requestAnimationFrame 비교
마지막으로 setInterval과 requestAnimationFrame을 사용하여 작성한 애니메이션 작업을 비교해보자.
See the Pen Untitled by 신건우 (@huhydupg-the-reactor) on CodePen.
setInterval(위)를 사용한 애니메이션 작업보다 requestAnimationFrame(아래)을 사용한 애니메이션이 더 부드럽게 동작하는 것을 볼 수 있다.
'개발 지식 정리 > Javascript' 카테고리의 다른 글
async/await (0) | 2023.09.14 |
---|---|
Promise (0) | 2023.09.09 |
이벤트 루프 (0) | 2023.07.21 |
타이머 (0) | 2023.07.18 |
DOM (0) | 2023.07.14 |