개요
[1] 브라우저 렌더링
[2] 애니메이션
[3] requestAnimationFrame
[4] requestAnimationFrame 사용법
[5] reference
[1] 브라우저 렌더링
우선 브라우저 렌더링에 대해 간략하게 알아야한다.
아래의 순서대로 순차적으로 모두 진행 후 렌더링이 완료된다.
1. DOM 생성
2. CSSOM 생성
3. Layout (Reflow)
4. Paint (Repaint)
5. Composite
Layout은 DOM과 CSSOM을 통해 Render Tree를 생성하는 단이다. (화면의 구조를 계산)
노드의 추가 및 제거 실행시 Layout이 다시 실행된다.
노드의 화면상 위치 및 크기 변경시 다시 실행된다.
Paint는 Render Tree의 각 요소들이 픽셀단위로 화면에 그려지는 단계이다.
노드의 화면상 위치 및 크기가 변하지 않는 선에서 스타일 변경시 해당 단계부터 실행한다.
display : none 사용시 해당 단계부터 실행한다.
HTML을 파싱할 때, 인라인 스타일을 만나면 Reflow가 발생한다. (레이어 다시 )
애니메이션이 있는 노드는 position : absolute 또는 fixed를 통해 Reflow 발생을 방지하는게 좋다.
[2] 애니메이션
CSS를 통한 애니메이션 효과가 비용이 가장 적지만 제어의 한계가 존재한다.
CSS 외적인 부분을 제어하고 싶을땐 JS를 사용해야한다.
이때, JS를 통해 애니메이션을 줄 수 있는 가장 쉬운 방법은 setInterval을 사용하는 방법이다.
setInterval(function animation() {
// . . . style 변경등의 DOM 제어
}, 1000 / 60); // 1초에 60번 실행 (60 프레임)
위와 같은 방법으로 setInterval(callback, delay)를 통해
delay 마다 callback을 실행시켜 애니메이션을 줄 수 있다.
그런데 setInterval을 통한 애니메이션은 단점들이 있다.
1. setInterval의 delay 옵션은 callback이 반복적으로 실행될 시간을 정해주는 것이지 애니메이션 프레임을 설정하는 것이 아니다.
2. setInterval은 delay후의 실행을 보장하진 않는다.
자바스크립트가 싱글 스레드에서 비동기를 지원하는 방식 때문인데 Event Loop는 Call Stack이 비는 시점에 Callback Queue의 원소를 Call Stack으로 옮기고 Call Stack에 들어가야 callback은 실행된다.
이때, delay후 Call Stack이 비어있지 않다면 callback은 바로 실행되지 않는다.
추가적으로 위와 같은 callback은 Promise.then()에 우선순위가 밀려 실제 서비스에선 delay후 실행을 보장할 수 없다.
(Event Loop, Macrotask, Microtask는 나중에 정리)
추가적으로 위의 이유로 같은 delay 값을 사용할 때, setInterval을 여러개 걸어놓는 것 보다 하나의 callback을 걸어 놓는게 효과적이다. (Call Stack에 옮길때 한번에 옮기게)
// 보단
setInterval(animation1, 200);
setInterval(animation2, 200);
setInterval(animation3, 200);
// 총 실행시간이 3 ~ 4ms를 넘지 않는 선에서
// 아래와 같이 묶는게 효과적
setInterval(() => {
animation1();
animation2();
animation3();
}, 200);
위와 같은 setInterval의 단점을 극복하고 callback들을 효과적으로
관리할 수 있는 방법이 있다.
setInterval을 통한 애니메이션들의 단점을 극복할 수 있는 자바스크립트 내장 함수가 있다.
[3] requestAnimationFrame
해당 함수는 위의 단점들을 극복하는 것 뿐만아니라 추가적인 장점이 많은 함수이다.
기본적으로 아래와 같은 구조를 갖는다.
const animationCallback = (timeStamp) => { // timeStamp는 requestAnimationFrame이 준다
// . . . DOM 제어 및 스타일 변경 로직
if (재귀 조건) { // 재귀 조건은 주로 timeStamp를 이용
// 한번만 실행되기 때문에 재귀를 돌려야한다
// 대충 10,000번 이상은 안됨
requestAnimationFrame(animationCallback);
}
};
// 등록
requestAnimationFrame(animationCallback);
//window.requestAnimationFrame(animationCallback)
우선 requestAnimationFrame을 통해 callback을 실행하면,
callback은 자동적으로 디스플레이의 주사율에 맞춰서 실행된다는 것이다.
사용자가 60hz를 사용하던 144hz를 사용하던 상관없이 사용자의 디스플레이에 맞춰진다.
추가적으로 setInterval은 delay만 지나면 repaint를 요청하지만 (callback으로 style변경시)
requestAnimationFrame을 통한 callback은
다음 repaint가 진행되기 전에 애니메이션 업데이트를 요청한다. (렌더링 프로세스 고려)
requestAnimationFrame을 사용하면 페이지가 비활성화인 상태일 때 repaint 작업이 멈춘다.
애니메이션이 일시중지 되므로 CPU 리소스를 낭비하지 않는다.
setInterval로 애니메이션을 돌리면 clearInterval될 때 까지 그냥 백그라운드에서 계속 돈다.
Event Loop에는 Animation Frames라는 requestAnimationFrame 전용 Queue가 존재한다.
When you call requestAnimationFrame, it adds the animation callback function into the queue. The queue is emptied at the next repaint, and all functions in the queue are run in order to update the animations.
If you call requestAnimationFrame again within the animation callback function, it will add it back to the queue, ensuring the animation continues for the next frame. This is the basis of creating a loop for animations in JavaScript.
Animation Frames의 우선순위는 Microtask (Promise) 보다는 낮고, MacroTask 보다는 높다.
Animation Queue(requestAnimationFrame)의 우선순위는 Task Queue(setInterval) 보단 높으니 다른 Web APIs의 콜백보단 먼저 실행되는게 보장된다.
[4] requestAnimationFrame 사용법
아래와 같은 탬플릿을 사용하면 사용하기가 쉽다.
const animate = ({timing, draw, duration}) => {
const start = performance.now();
const animatationCallback = (time) => {
const timeFraction = (time - start) / duration;
const progress = timing(timeFraction);
draw(progress);
if (timeFraction < 1) { // 100% 안넘으면 재귀
requestAnimationFrame(animatationCallback);
}
};
// 실제 등록
requestAnimationFrame(animatationCallback);
};
// 애니메이션 새부 설정
const timing = (timeFraction) => timeFraction; // transition-timing-function : linear
const draw = (progress) => {
// ...DOM 제어 및 스타일링 로직
Element.style.left = progress + 'px';
};
// 등록
animate({
timing,
draw,
duration: 1000,
});
timing 함수로 아래와 같은 예제들이 있다.
function quad(timeFraction) {
return Math.pow(timeFraction, 2)
}
function circ(timeFraction) {
return 1 - Math.sin(Math.acos(timeFraction));
}
function back(x, timeFraction) {
return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x)
}
function bounce(timeFraction) {
for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
}
}
function elastic(x, timeFraction) {
return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction)
}
위의 함수 또는 linear 함수에 EaseIn, EaseOut, EaseInOut 같은 효과들도 줄 수 있다. (wrap)
// easeIn은 따로 공식이 없는 것 같다.
function makeEaseOut(timing) { // easeOut
return function(timeFraction) {
return 1 - timing(1 - timeFraction);
}
}
function makeEaseInOut(timing) { // easeInOut
return function(timeFraction) {
if (timeFraction < .5)
return timing(2 * timeFraction) / 2;
else
return (2 - timing(2 * (1 - timeFraction))) / 2;
}
}
위의 timing 함수와 ease 함수들을 통한 결과는 아래의 사이트에서 한번에 확인할 수 있다. (Math 함수 포함)
requestAnimationFrame은 사용하지 않을 이유가 없는 것 같다.
requestAnimationFrame이 Node에서는 제공되지 않는다고 한다.
[5] reference
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
https://web.dev/articles/optimize-javascript-execution?hl=ko
http://www.devdic.com/javascript/reference/window/method:2249/requestAnimationFrame()
https://ko.javascript.info/js-animation
https://codedamn.com/news/javascript/what-is-requestanimationframe-queue-in-javascript
https://beomy.github.io/tech/browser/reflow-repaint/
'Javascript > Javascript 기초' 카테고리의 다른 글
[Javascript] Execute Context, Lexical Environment, Closure, Promise, Async/Await 요약 (1) | 2024.08.08 |
---|---|
[Javascript] DOM, HTML DOM API, document (0) | 2024.05.04 |
[Javascript] 자바스크립트 특징 (0) | 2024.03.17 |
[Javascript] Ajax 개념, 사용 (회원가입시 ID 중복체크) (0) | 2022.07.05 |
[Javascript] JSON 작성, 사용 (Javascript Object Notation) (0) | 2022.06.23 |