requestAnimationFrame , 실험과 폴리필을 통해 살펴보기

ChoiYongHyeun·2024년 2월 9일
6

브라우저

목록 보기
4/16
post-thumbnail

살펴보기를 시작한 이유

구글 개발자 도구 설명서에서 예시로 제공하는 페이지다.

저번 Reflow , Repaint 에 대한 개념을 공부해봤고

최적화를 하기 위해서는 reflow 를 지양하고 repaint 만을 이용해 인터렉티브한 효과를

줘야 한다는 교훈을 얻었다.

이러한 교훈을 직접 토이프로젝트처럼 구현해보기 위해 위의 페이지를 직접 구현해보려고 했다.

위 페이지에서 Optimize 하는 방법은 각 노드들의 offsetTOP 을 계산을 하느냐 마느냐로

reflow 를 일으키거나 일으키지 않는 방법을 이용했더라

코드는 위의 링크에 들어가 개발자도구에서 네트워크 - 자바스크립트 파일을 열어보면 볼 수 있다.

Optimize 를 설정했을 때

Optimize 를 설정하지 않았을 때


한 번의 repaint 를 위해 걸리는 시간이 optimize 를 설정했을 때는 약 4ms4ms 밖에 걸리지 않지만 optimize 를 설정하지 않았을 때는 40ms40ms 이상 걸린다.

그래서 버벅이는 것처럼 보이는 이유도 optimize 를 설정했을 땐 1초에 15프레임씩 업데이트 되지만 optimize 를 설정하지 않았을 땐 1초에 1.25프레임씩밖에 업데이트 되지 않기 때문에 버벅이는 것 처럼 보이는 것이다.

노드들을 이동 시키는 것은 top 의 값을 변경시켜서 결국 각 노드 별로 reflow 가 일어나는 것은 동일했다.

그래서 나는 해당 방법이 reflow 를 그대로 일으키니 optimize 가 안되는거 아닌가 ? 싶었는데
브라우저의 렌더링 엔진이 지연평가를 사용하여 계산하는 행위를 지연해뒀다가
한 번의 reflow 과정에서 우루루 업데이트 하는 것 같다.

그래서 아 ~ 그래 그런식으로 optimize 의 예시를 들었구나 생각을 했는데

위의 애니메이션을 가능하게 한 requestAnimationFrame 에 대한 코어 지식이 부족한 것 같아

따로 공부를 해봤다.


FPS (Frame per second)

FPS 는 이름처럼 초당 나타나는 프레임의 수를 의미한다.

초당 프레임의 횟수가 높을 수록 애니메이션의 움직임이 자연스럽게 느껴진다.

1945400-FPS-frames-per-second

애니메이션이 보다 더 부드럽기 위해선 높은 FPS 를 사용하면 되지만

인간의 눈은 초당 60프레임, 그러니 60FPS 만 되어도 애니메이션이 부드럽다고 인식하기에

프레임의 개수는 60프레임이면 충분하다.

물론 다다익선이긴 하다. 그래서 모니터같은 경우도 144hz 의 모니터를 이용하다가 60hz 모니터를 이용하면 버벅이는 것처럼 느껴지기도 한다.


requestAnimationFrame

MDN 에서 제공하는 requestAnimationFrame 에 대한 설명 중 일부이다.

requestAnimationFrame 은 콜백 함수를 시행 후 repaint 를 시행한다고 이야기 한다.

requestAnimationFrame 자체는 하나의 프레임이기에 하나의 장면이다.

내가 어떤 애니메이션을 위해 60번의 장면이 필요하다면 requestAnimationFrame 을 60번 재귀적으로 호출하면 된다.

정리

requestAnimationFrame 은 원하는 프레임을 만들어주는 함수를 재귀적으로 호출해 지속적 리페인트로 애니메이션 효과를 준다.

콜백함수에게는 인수로 DOMHighResTimeStamp 를 전달한다고 하는데 이는 이따가 실습을 통해 알아보자

반환값으론 setTimeout 이나 setInterval 처럼 해당 애니메이션의 ID 값을 반환한다고 하며 이를 통해 cancleAnimationFrame 으로 프레임을 제거 할 수 있다고 한다.

가벼운 실습을 통해 알아보자


FPSrequestAnimationFrame 의 관계

어제 글을 쓰고 나서 내가 쓴 글을 다시 읽어봤는데
정리가 너무 안된 것 처럼 느껴졌다.
왜일까 생각을했더니 직관적인 예시가 없었던 것 같다.

let startTime, lastTime, endTime;
let curFrame = 0;
const maxFrame = 60; // 제작할 프레임의 최대 개수
const frameInterval = [];

const callbackFn = (timeStamp) => {
  if (!startTime) {
    // 콜백함수가 처음 호출되면 startTime , lastTIme 설정
    startTime = timeStamp;
    lastTime = timeStamp;
  }
  if (curFrame >= maxFrame) {
    endTime = timeStamp;
    const result = {
      전체소요시간: endTime - startTime,
      전체컷수: frameInterval.length,
      프레임별인터벌:
        frameInterval.reduce((pre, cur) => pre + cur) / frameInterval.length,
    };
    console.table(result);
    return;
  }

  frameInterval.push(timeStamp - lastTime);
  lastTime = timeStamp;
  curFrame += 1;
  requestAnimationFrame(callbackFn);
};

requestAnimationFrame(callbackFn);

requestAnimationFrame 은 자동으로 사용자가 수용 할 수 있는 범위 내에서 최대한 부드러운 애니메이션을 제공하기 위해

프레임의 개수에 따라 적절한 간격으로 repaint 한다.

위 코드는 maxFrame 의 수만큼 requestAnimationFrame 을 재귀적으로 호출하는 함수이다.

결과물을 보자

전체 60장의 재귀함수의 호출은 약 1000ms 동안 걸렸으며

각 평균 프레임 별 간격 시간은 16.4 ms 인 모습을 볼 수 있다.

위에서 설명했듯 우리 눈에 부드러운 애니메이션을 위해선 FPS 가 60장이라고 했으니

1000 ms / 60 = 16.77.. , 약 16ms 에 한 번씩만 리페인트 되어도 우리는 부드러운 애니메이션이라 인식 할 수 있다.

180 프레임의 애니메이션을 설정해도 애니메이션의 FPS60 이 되도록 최적화 하여 콜백함수를 시행하고 있다.

그래프로 나타내면 다음과 같은 모습이다.

정리

requestAnimationFrame 은 애니메이션을 이루는 프레임들을 부드럽게 구성하기 위해
콜백함수의 호출 간격을 초당 60회로 자동적으로 설정한다.

콜백함수의 시행이 16.67ms 이상 걸리는 무거운 경우에는 지연된다.

PS
현재 나의 모니터는 60FPS 만 지원하기 때문에 최적의 FPS 가 60으로 취급되어 16.67ms 에 한 번씩 리페인트 되었다.
하지만 만약 모니터가 더 높은 FPS 도 지원한다면 requestAnimationFrame 은 더 부드러운 애니메이션을 위해 자동으로 더 높은 프레임 수와 짧은 간격으로 리페인트 할 것이다.


🚀 로켓을 날려봅시다

const $rocket = document.querySelector('.rocket');
let rocketHeight = 95;
const launchRocket = () => {
  $rocket.style.transform = `translateY(${rocketHeight}vh)`;
  rocketHeight -= 1;
  if (rocketHeight < 0) return; // 재귀 함수 탈출 조건 
  requestAnimationFrame(launchRocket); // 재귀함수를 이용해 높이 지속적 변경
};

launchRocket();

launchRocket 콜백함수는 각 프레임마다 로켓의 높이를 변경하도록 설정되어 있으며

로켓의 높이를 변경한 후 requestAnimationFrame 을 이용해 repaint 한다.

재귀적으로 로켓이 최정상까지 닿을 때 까지 시행되며 최정상에 닿으면 재귀 함수를 탈출한다.

실제 개발자도구의 performance 에서 살펴보면 Animation Frame Fired 되면 repaint 하는 모습을 볼 수 있다.

위의 launchRocket 같은 경우엔 95 부터 0이 될 때 까지 재귀함수를 호출 했으니

총 95개의 프레임을 지속적으로 리페인트 한 것이다.

그럼 이번엔 로켓이 날아가는 시간을 구해 몇 초동안 지속적으로 날아 갈 수 있도록 해보자

🚀 로켓을 3초동안 날려봅시다

const $rocket = document.querySelector('.rocket');
const $p = document.querySelector('p');
let startTime, rocketLocation;
let timeDiff = 0;
const maxTimeDiff = 3000;

const stepLaunch = (timeStamp) => {
  // timeStamp 는 ms 단위
  if (!startTime) startTime = timeStamp;
  timeDiff = Math.round(timeStamp - startTime);
  $p.textContent = `${timeDiff}`.padStart(4, '0') + 'ms';
  if (timeDiff >= maxTimeDiff) return;
  rocketLocation = 95 * (1 - timeDiff / maxTimeDiff);
  $rocket.style.transform = `translateY(${rocketLocation}vh)`;
  requestAnimationFrame(stepLaunch);
};

stepLaunch();

requestAnimationFrame 이 콜백함수에게 전달하는 timeStamp 인수는

requestAnimationFrame 이 시행된 시간 (ms단위 ) 으로부터 증가하는 시간들을 제공한다.

표현하기가 어렵다 .. 만약 requestAnimationFrame 이 실행된 시점의 ms 이 500 이였다면
500ms 흘렀을 때의 절대적 ms 는 0이겠지만 requestAnimationFrame 에서 제공하는 인수는
500ms + 500ms1000ms 를 제공한다.

이렇게 각 프레임 별 애니메이션에 사용할 프레임들을 시간 별로 업데이트하는 것은 마치

setInterval 과 유사한 느낌이 든다.

하지만 requestAnimationFrame 를 이용하면 리소스 집약적이며 부드러운 애니메이션을 제공 할 수 있다.

그 이유에 대해 살펴보자


requestAnimationFrame vs setInterval

위처럼 어떤 시간마다 애니메이션 효과를 주고 싶다면 setInterval 을 사용하는 방법도 존재 할 것이다.

그럼 requestAnimationFramesetInterval 을 사용 할 떄의 차이를 살펴보자

requestAnimationFrame 이 리소스를 덜 잡아먹을까 ?

서칭했던 내용

requestAnimationFramesetInterval 에 비해 리소스 집약적인 이유는

requestAnimationFrame브라우저가 repaint 할 준비가 된 경우에만 실행된다는 것이다.

setInterval 은 브라우저의 상태에 따라 상관 없이 타이머는 인수로 전달한 인터벌마다 실행된다.

그 말은 브라우저가 비활성화 되어있거나 숨겨져있을 때에도 setInterval 은 지속적으로 전달받은 콜백 함수를 시행하고 있기에 불필요한 상황에서도 리소스를 사용하게 된다.

해당 내용에서 나는 requestAnimationFrame 이 리소스를 덜 잡아먹을 것이라고 생각했고 서칭한 것들에서도 그렇다고 했다.

하지만 실습해보니 실상은 달랐다.

예시를 통해 살펴보자

앞으로 동일한 애니메이션임에도 setInterval 일 때와 requestAnimationFrame 일 때의 리페인트 여부를 살펴보도록 하자

이렇게 애니메이션을 틀어두고 다른 창을 탭하여 현재 탭을 비활성화 해보도록 하겠다.

setInterval 을 사용 했을 때

const $rocket = document.querySelector('.rocket');
let angle = 0;
const rotateRocket = () => {
  $rocket.style.transform = `rotateZ(${angle % 360}deg)`;
  angle += 1000 / 60;
};

setInterval(rotateRocket, 1000 / 60);

setInterval 을 이용했을 때에도 브라우저가 비활성화 되어있으면 리페인트를 하지 않는다.

물론 브라우저가 비활성화 되어있더라도 Timer 는 지속적으로 Fired 되고 있다.

requestAnimationFrame 을 이용 했을 때

const rotateRocket = () => {
  $rocket.style.transform = `rotateZ(${angle % 360}deg)`;
  angle += 1000 / 60;
  requestAnimationFrame(rotateRocket);
};
requestAnimationFrame(rotateRocket);

requestAnimationFramesetInterval 과 다르게 프레임의 수를 지정해주지 않아도 된다.
자동으로 최적화가 되기 때문인데 이는 나중에 설명하겠다.

requestAnimationFrame 을 사용하면

다른 페이지를 보고 있을 때에는 아무런 액션이 없는 모습을 볼 수 있다.

이런 점은 페이지가 비활성화 되어 있더라도 타이머가 점화되던 setInterval 와는 다른 점임을 알 수 있다.

하지만 둘 다 repaint 를 사용하지 않는다는 점은 동일하다.

둘 다 동일하게 페이지를 벗어났을 때 repaint 되지 않는 이유

브라우저는 리소스를 절약하기 위해 페이지에 벗어나면
해당 페이지에 대한 Javascript 의 실행을 일시 중단한다고 한다.

requestAnimationFrame 은 보다 더 부드러운 애니메이션을 제공해줄 수 있다

리소스 집약적이라는 내용이 완벽하게 정확한 것은 아니였지만

확실한 것은 requestAnimationFramesetInterval 보다 부드러운 화면을 제공하는데 있어 장점이 있다는 사실은 확실하다.

위 예시 코드에서 살펴보면

/* setInterval 사용 */
const $rocket = document.querySelector('.rocket');
let angle = 0;
const rotateRocket = () => {
  $rocket.style.transform = `rotateZ(${angle % 360}deg)`;
  angle += 1000 / 60;
};

setInterval(rotateRocket, 1000 / 60);
/* requestAnimationFrame 사용 */
const rotateRocket = () => {
  $rocket.style.transform = `rotateZ(${angle % 360}deg)`;
  angle += 1000 / 60;
  requestAnimationFrame(rotateRocket);
};
requestAnimationFrame(rotateRocket);

requestAnimationFrame 의 경우 setInterval 과 다르게 프레임의 수를 따로 지정해주지 않은 모습을 볼 수 있을 것이다.

이런게 가능한 이유는

requestAnimationFrame은 사용자의 환경에 맞춰 적합한 FPS 를 설정, 프레임의 간격에 맞춰 콜백함수의 실행을 보장하기 때문이다.

requestAnimationFrame 의 폴리필을 살펴보자

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
 
    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

requestAnimationFrame polyfill

해당 코드는 60FPS 를 기준으로 설정한 것이다.

requestAnimationFramesetTimeout 을 이용하여 콜백 함수를 최대 16ms 의 간격을 두고 실행할 것을 보장하며 콜백함수가 무거워 재귀함수가 16ms 를 넘어 호출되더라도 콜백함수의 호출과 렌더링을 보장한다.

16ms 라는 시간이 등장한 이유는 1초당 60프레임을 보장하기 위해선, 1000ms/60frame 으로 적어도 16ms 에 한 번씩 콜백함수와 함께 리페인트 되어야 하기 때문이다.

timeToCall = Math.max(0 , 16-(currTime - lastTime)) 에서 Math.max 를 이용하는 이유는
콜백함수가 16ms 이상의 시간을 소요했을 경우 바로 실행될 수 있도록 하기 위함이다.

콜백함수가 너무 무거울 수도 있으니까 !

렌더링 보장이 부드러운 애니메이션을 제공하는 이유

HTML , CSS

실험을 통해 살펴보자

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div>
      <div class="interval">setInterval</div>
      <div class="animation">requestAnimation</div>
      <button>테스트 시작!</button>
    </div>
  </body>
  <script src="script.js"></script>
</html>
* {
  padding: 0px;
  margin: 0px;
}

div {
  padding: 10px;
}

.interval,
.animation {
  display: flex;
  justify-content: flex-end;
  color: white;
  font-weight: 900;
  width: 200px;
  margin: 10px;
}

.interval {
  background-color: orange;
}
.animation {
  background-color: tomato;
}

button {
  padding: 10px;
}

이제 테스트 시작 버튼을 누르면 각 div 태그들은 각기 다른 방법을 이용해 body 태그의 끝까지 늘어날 것이다.

늘어나는 동안 각 콜백함수가 호출되어 리페인트 되는 간격을 살펴보자

사용할 노드 및 객체 생성

const $button = document.querySelector('button');
const $interval = document.querySelector('.interval');
const $animation = document.querySelector('.animation');
const $body = document.querySelector('body');
const maxWidth = $body.clientWidth;

const intervalInfo = {
  size: $interval.clientWidth,
  node: $interval,
  maxWidth,
};

const animationInfo = {
  size: $animation.clientWidth,
  node: $animation,
  maxWidth,
};

실험에 사용할 노드들을 querySelector 를 이용해 가져오고

한 번에 사용하기 좋게 객체 형태로 캡슐화 해줬다.

기록을 기록할 Recorder 클래스 생성

class Recorder {
  constructor(startTime) {
    this.startTime = startTime;
    this.intervalList = [];
    this.timeList = [];
  }

  setTimeline(curTime, lastTime) {
    const { startTime, intervalList, timeList } = this;
    intervalList.push(curTime - lastTime); // 이전 호출과의 간격
    timeList.push(curTime - startTime); // 시작부터의 간격 , 선형적일 수록 타임라인이 일정한 호출
  }

  generateResult(curTime) {
    const { startTime, intervalList } = this;
    const { length } = intervalList;

    const totalInterval = intervalList.reduce((pre, cur) => pre + cur);

    this.result = {
      maxInterval: Math.max(...intervalList.slice(1)),
      minInterval: Math.min(...intervalList.slice(1)),
      avrageInterval: totalInterval / length,
      elapseTime: curTime - startTime,
      averageFrame: Math.round(1000 / (totalInterval / length)),
    };
    return this.result;
  }
}

각 실험에 사용할 Recorder 객체를 생성해주었다.

setTimeline 메소드를 통해 repaint 주기와 콜백 함수의 호출의 시간적인 모습을 살펴보도록 하자

그리고 generateResult 메소드를 통해서 여태까지의 repaint 의 정보들을 요약한 테이블을 생성하고 로그하도록 하자

repaint 메소드 생성

const repaintNode = (nodeInfo) => {
  const needStop = nodeInfo.size >= nodeInfo.maxWidth;
  nodeInfo.node.style.width = `${nodeInfo.size}px`;
  nodeInfo.size += 1;

  return needStop;
};

repaintNode 함수는 특정 노드의 width 를 증가시키고 maxSize 보다 큰지 아닌지를 나타내는 boolean 값을 반환하는 함수이다.

needStop 을 통해 각 타이머 혹은 재귀함수를 탈출할지 말지를 결정 할 것이다.

setIntervalFunc

const setIntervalFunc = (nodeInfo) => {
  const DELAY = 1000 / 60;
  const startTime = new Date().getTime();
  const params = {
    startTime,
    lastTime: startTime,
    recorder: new Recorder(startTime),
  };

  const timer = setInterval(() => {
    const { recorder, lastTime } = params;
    const curTime = new Date().getTime();

    recorder.setTimeline(curTime, lastTime); // 타임라인 기록
    const needStop = repaintNode(nodeInfo);
    if (needStop) {
      const result = recorder.generateResult(curTime);
      console.log('setInterval 의 애니메이션 테이블');
      console.table(result);

      clearInterval(timer);
    }

    params.lastTime = curTime;
  }, DELAY);
};

setIntervalFunc1000/ 60 간격으로 호출되어 60FPS 를 유지하면서 리페인트 되기를 기대한다.

setAnimationFunc

const setAnimationFunc = (nodeInfo) => {
  const startTime = new Date().getTime();
  const params = {
    startTime,
    lastTime: startTime,
    recorder: new Recorder(startTime),
  };

  const callbackFn = (timeStamp) => {
    const { recorder, lastTime } = params;
    const curTime = startTime + timeStamp;

    recorder.setTimeline(curTime, lastTime);
    const needStop = repaintNode(nodeInfo);
    if (needStop) {
      const result = recorder.generateResult(curTime);
      console.log('requestAnimation 의 애니메이션 테이블');
      console.table(result);

      return;
    }
    params.lastTime = curTime;
    requestAnimationFrame(callbackFn);
  };
  requestAnimationFrame(callbackFn);
};

setAnimationFuncrequestAnimationFunc 을 이용해 콜백함수를 재귀적으로 호출한다.

이벤트 핸들러 등록

$button.addEventListener('click', () => {
  setIntervalFunc(intervalInfo);
  setAnimationFunc(animationInfo);
});

이제 버튼을 누르면 실험이 시작된다.

시작해보자

⭐ 결과 해석

실제로 보면 setInterval 로 설정된 애니메이션은 조금씩 버벅이는 듯한 모습을 보인다.

그 이유를 살펴보면

setInterval , requestAnimationFrame 모두 약 60FPS 를 잘 유지했지만

setInterval 의 경우에는 interval 사이의 간격의 최대값은 18ms , 최소값은 14ms 처럼 간격이 큰 모습을 볼 수 있다.

각 프레임별 간격이 일정 할 수록 부드러운 애니메이션을 제공 할 수 있음에도 불구하고 setInterval 최대 4ms 의 간격을 보이고 있다.

그에 비해 requestAnimationFrame 의 경우에는 간격의 최대값과 최소값의 차이가 약 0.6ms 밖에 나지 않는 일관적인 모습을 보인다.

이로 인해 requestAnimationFrame 이 더 부드러운 모습을 보인다.

이처럼 간격이 일정하고 간격이 짧을 수록 부드러운 애니메이션을 제공 할 수 있다.

setInterval 에서 이렇게 프레임 사이의 간격이 차이가 났던 이유로는 다음과 같이 생각된다.

  1. setInterval 의 타이머의 간격은 1000/ 60 ms 로 매우 짧은 간격으로 점화된다. 이는 비동기적으로 이벤트 루프에서 콜스택으로 1000/ 60 ms 마다 콜백함수를 보내 호출하겠다는 것이다. 이 짧은 간격 사이에 콜스택에 다른 함수가 존재 할 때 이벤트 루프에서 콜백 함수들이 쌓이며 지연될 수 있다.

  2. 웹 브라우저의 최적화로 인해 setInterval 의 콜백 함수가 페이지의 다른 작업의 전체 성능에 영향을 주지 않고 지정된 간격보다 일찍 실행 할 수 있음을 감지하면 더 일찍 실행 하기도 한다. 이로인해 콜백 함수의 실행 타이밍이 달라졌을 수 있다.

  3. 짧은 시간동안 빠르게 함수를 호출하면서 장치의 부하가 높아지거나 리소스가 제약이 있는 경우 setInterval 콜백을 포함한 코드 실행이 영향을 받는다.

    부하가 높아지면 우선순위가 높은 다른 작업부터 시행한다.
    이로 인해 setInterval 의 실행 타이밍이 달라질 수 있다.

  4. 가장 근본적인 이유로 setInterval 같은 경우는 렌더링을 위해 고안된 콜백 함수가 아니기 때문에 브라우저 렌더링 파이프라인 및 디스플레이 새로 고침 빈도와 동기화 되어있지 않다. 그로 인해 렌더링 유무와 상관없이 호출되기 때문에 프레임이 그려지지 않았어도 콜백함수가 호출되어 size 가 증가했을 것이다. 이로 인해 2프레임동안 1px , 1px 씩 증가했어야 하는 애니메이션이 2프레임동안 한 번만 리페인트되고 2px가 증가하는 등의 모습이 보이는 것이다.

requestAnimationFrame 에서는 프레임 사이의 간격이 일관된 이유는 다음과 같이 생각된다.

  1. requestAnimationFrame 은 콜백 함수의 실행을 브라우저의 렌더링 파이프라인 및 디스플레이 새로 고침 빈도와 동기화 되어있다. 이를 통해 최적의 시간에 렌더링 되어 원활한 성능을 보장한다.

    requestAnimationFrame 은 이전 렌더링이 종료 된 후 콜백함수를 호출하도록 하여 모든 프레임들이 리페인트 될 수 있도록 최적화 한다. 이를 통해 버벅이는 모습이 나타나지 않는다.

  2. requestAnimationFrame 은 애니메이션 작업의 우선순위를 지정함으로서 버벅거림 가능성을 최소화 하고 보다 원활한 사용자 경험을 제공한다.

  3. requestAnimationFrame 은 근본적으로 Javascript 실행 스레드와 별개인 Composition Thread 에서 실행된다. 이러한 분리를 통해 애니메이션을 다른 Javascript 작업과 독립적으로 렌더링 하는 것이 가능하여 일관성과 성능이 향상된다.

requestAnimationFrame 의 딥다이브를 위해서는 브라우저 렌더링 파이프라인 에 대한 공부를 따로 더 해봐야겠다.
정말 운이 좋게도 찾아 볼 수 있는 포스트나 영상들이 많더라 :)


회고

어찌저찌 하다보니

내용이 requestAnimation 에 대한 내용보다 setInterval 에 더 가까이 들어간 것 같기는 하지만 그래도 만족스럽다

optimize-scroll-event
해당 게시글을 보면 requestAnimaion 이 어떻게 비동기 처리가 되는지에 대해 감을 잡을 수 있다.
requestAnimaion 도 비동기 처리지만 setTimeout 과 같은 이벤트 루프가 아닌
animaion frame 이란 별도의 태스크 큐에서 처리된다.
정말 좋은 글들이 많은 블로그다. 시간 내서 천천히 읽어봐야겠다.

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글