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

ChoiYongHyeun·2024년 2월 9일
7

브라우저

목록 보기
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개의 댓글

관련 채용 정보