╱╱╭╮╱╱╱╱╱╱╭━━━╮╱╱╱╭╮╱╭╮╱╱╱╱╱╱ ╱╱┃┃╱╱╱╱╱╱┃╭━╮┃╱╱╱┃┃╱┃┃╱╱╱╱╱╱ ╱╱┃┣━━┳━━╮┃┃╱┃┣━╮╱┃╰━╯┣━━┳━╮╱ ╭╮┃┃╭╮┃┃━┫┃╰━╯┃╭╮╮┃╭━╮┃╭╮┃╭╮╮ ┃╰╯┃╭╮┃┃━┫┃╭━╮┃┃┃┃┃┃╱┃┃╭╮┃┃┃┃ ╰━━┻╯╰┻━━╯╰╯╱╰┻╯╰╯╰╯╱╰┻╯╰┻╯╰╯

Javascript/Javascript 기초

[Javascript] 자바스크립트 애니메이션, requestAnimationFrame

재안안 2024. 5. 8. 19:48

개요

[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 함수 포함)

 

https://easings.net/

 

Easing Functions Cheat Sheet

Easing functions specify the speed of animation to make the movement more natural. Real objects don’t just move at a constant speed, and do not start and stop in an instant. This page helps you choose the right easing function.

easings.net

 

requestAnimationFrame은 사용하지 않을 이유가 없는 것 같다.

requestAnimationFrame이 Node에서는 제공되지 않는다고 한다.

 

 

[5] reference

https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

 

Window: requestAnimationFrame() method - Web APIs | MDN

The window.requestAnimationFrame() method tells the browser you wish to perform an animation. It requests the browser to call a user-supplied callback function before the next repaint.

developer.mozilla.org

 

https://web.dev/articles/optimize-javascript-execution?hl=ko

 

자바스크립트 실행 최적화  |  Articles  |  web.dev

JavaScript는 종종 시각적 변화를 유발합니다. 어떤 경우에는 스타일 조작을 통해 직접 구현되거나, 어떤 경우에는 데이터 검색 또는 정렬과 같은 시각적 변화를 일으키는 계산을 통해 구현됩니다.

web.dev

 

http://www.devdic.com/javascript/reference/window/method:2249/requestAnimationFrame()

 

requestAnimationFrame()::JavaScript 레퍼런스

requestAnimationFrame() 메소드는 비동기 함수로서 CSS 애니메이션으로는 처리가 어렵거나 canvas, SVG 등의 애니메이션을 직접 구현하고자 할 때 사용된다. 이 메소드는 애니메이션 구현에 필요한 타임

www.devdic.com

 

https://ko.javascript.info/js-animation

 

JavaScript animations

 

ko.javascript.info

 

https://codedamn.com/news/javascript/what-is-requestanimationframe-queue-in-javascript

 

What is requestAnimationFrame Queue in JavaScript?

Welcome to codedamn, where we make learning to code a breeze. Today, we're diving deep into the world of JavaScript, specifically a crucial function, known as requestAnimationFrame Queue. If you're a front-end developer or aspiring to be one, you'll find t

codedamn.com

 

https://beomy.github.io/tech/browser/reflow-repaint/

 

[Browser] Reflow와 Repaint

브라우저의 화면이 수정될 때, 렌더링 과정을 최적화 하는 방법에 대해 이야기 할 것입니다.

beomy.github.io