구글 개발자 도구 설명서에서 예시로 제공하는 페이지다.
저번 Reflow , Repaint
에 대한 개념을 공부해봤고
최적화를 하기 위해서는 reflow
를 지양하고 repaint
만을 이용해 인터렉티브한 효과를
줘야 한다는 교훈을 얻었다.
이러한 교훈을 직접 토이프로젝트처럼 구현해보기 위해 위의 페이지를 직접 구현해보려고 했다.
위 페이지에서 Optimize
하는 방법은 각 노드들의 offsetTOP
을 계산을 하느냐 마느냐로
reflow
를 일으키거나 일으키지 않는 방법을 이용했더라
코드는 위의 링크에 들어가 개발자도구에서 네트워크 - 자바스크립트 파일을 열어보면 볼 수 있다.
Optimize
를 설정했을 때
Optimize
를 설정하지 않았을 때
한 번의repaint
를 위해 걸리는 시간이optimize
를 설정했을 때는 약 밖에 걸리지 않지만optimize
를 설정하지 않았을 때는 이상 걸린다.그래서 버벅이는 것처럼 보이는 이유도
optimize
를 설정했을 땐 1초에 15프레임씩 업데이트 되지만optimize
를 설정하지 않았을 땐 1초에 1.25프레임씩밖에 업데이트 되지 않기 때문에 버벅이는 것 처럼 보이는 것이다.
노드들을 이동 시키는 것은
top
의 값을 변경시켜서 결국 각 노드 별로reflow
가 일어나는 것은 동일했다.그래서 나는 해당 방법이
reflow
를 그대로 일으키니optimize
가 안되는거 아닌가 ? 싶었는데
브라우저의 렌더링 엔진이 지연평가를 사용하여 계산하는 행위를 지연해뒀다가
한 번의reflow
과정에서 우루루 업데이트 하는 것 같다.
그래서 아 ~ 그래 그런식으로 optimize
의 예시를 들었구나 생각을 했는데
위의 애니메이션을 가능하게 한 requestAnimationFrame
에 대한 코어 지식이 부족한 것 같아
따로 공부를 해봤다.
FPS (Frame per second)
FPS
는 이름처럼 초당 나타나는 프레임의 수를 의미한다.
초당 프레임의 횟수가 높을 수록 애니메이션의 움직임이 자연스럽게 느껴진다.
애니메이션이 보다 더 부드럽기 위해선 높은 FPS
를 사용하면 되지만
인간의 눈은 초당 60프레임, 그러니 60FPS
만 되어도 애니메이션이 부드럽다고 인식하기에
프레임의 개수는 60프레임이면 충분하다.
물론 다다익선이긴 하다. 그래서 모니터같은 경우도
144hz
의 모니터를 이용하다가60hz
모니터를 이용하면 버벅이는 것처럼 느껴지기도 한다.
requestAnimationFrame
MDN
에서 제공하는 requestAnimationFrame
에 대한 설명 중 일부이다.
requestAnimationFrame
은 콜백 함수를 시행 후 repaint
를 시행한다고 이야기 한다.
requestAnimationFrame
자체는 하나의 프레임이기에 하나의 장면이다.
내가 어떤 애니메이션을 위해 60번의 장면이 필요하다면 requestAnimationFrame
을 60번 재귀적으로 호출하면 된다.
정리
requestAnimationFrame
은 원하는 프레임을 만들어주는 함수를 재귀적으로 호출해 지속적 리페인트로 애니메이션 효과를 준다.
콜백함수에게는 인수로 DOMHighResTimeStamp
를 전달한다고 하는데 이는 이따가 실습을 통해 알아보자
반환값으론 setTimeout
이나 setInterval
처럼 해당 애니메이션의 ID
값을 반환한다고 하며 이를 통해 cancleAnimationFrame
으로 프레임을 제거 할 수 있다고 한다.
가벼운 실습을 통해 알아보자
FPS
와 requestAnimationFrame
의 관계어제 글을 쓰고 나서 내가 쓴 글을 다시 읽어봤는데
정리가 너무 안된 것 처럼 느껴졌다.
왜일까 생각을했더니 직관적인 예시가 없었던 것 같다.
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
프레임의 애니메이션을 설정해도 애니메이션의 FPS
가 60
이 되도록 최적화 하여 콜백함수를 시행하고 있다.
그래프로 나타내면 다음과 같은 모습이다.
정리
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개의 프레임을 지속적으로 리페인트 한 것이다.
그럼 이번엔 로켓이 날아가는 시간을 구해 몇 초동안 지속적으로 날아 갈 수 있도록 해보자
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 + 500ms
로1000ms
를 제공한다.
이렇게 각 프레임 별 애니메이션에 사용할 프레임들을 시간 별로 업데이트하는 것은 마치
setInterval
과 유사한 느낌이 든다.
하지만 requestAnimationFrame
를 이용하면 리소스 집약적이며 부드러운 애니메이션을 제공 할 수 있다.
그 이유에 대해 살펴보자
requestAnimationFrame
vs setInterval
위처럼 어떤 시간마다 애니메이션 효과를 주고 싶다면 setInterval
을 사용하는 방법도 존재 할 것이다.
그럼 requestAnimationFrame
과 setInterval
을 사용 할 떄의 차이를 살펴보자
requestAnimationFrame
이 리소스를 덜 잡아먹을까 ?서칭했던 내용
requestAnimationFrame
이setInterval
에 비해 리소스 집약적인 이유는
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);
requestAnimationFrame
은setInterval
과 다르게 프레임의 수를 지정해주지 않아도 된다.
자동으로 최적화가 되기 때문인데 이는 나중에 설명하겠다.
requestAnimationFrame
을 사용하면
다른 페이지를 보고 있을 때에는 아무런 액션이 없는 모습을 볼 수 있다.
이런 점은 페이지가 비활성화 되어 있더라도 타이머가 점화되던 setInterval
와는 다른 점임을 알 수 있다.
하지만 둘 다 repaint
를 사용하지 않는다는 점은 동일하다.
둘 다 동일하게 페이지를 벗어났을 때
repaint
되지 않는 이유브라우저는 리소스를 절약하기 위해 페이지에 벗어나면
해당 페이지에 대한Javascript
의 실행을 일시 중단한다고 한다.
requestAnimationFrame
은 보다 더 부드러운 애니메이션을 제공해줄 수 있다리소스 집약적이라는 내용이 완벽하게 정확한 것은 아니였지만
확실한 것은 requestAnimationFrame
이 setInterval
보다 부드러운 화면을 제공하는데 있어 장점이 있다는 사실은 확실하다.
위 예시 코드에서 살펴보면
/* 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);
};
}());
해당 코드는 60FPS
를 기준으로 설정한 것이다.
requestAnimationFrame
은 setTimeout
을 이용하여 콜백 함수를 최대 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);
};
setIntervalFunc
은 1000/ 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);
};
setAnimationFunc
은 requestAnimationFunc
을 이용해 콜백함수를 재귀적으로 호출한다.
$button.addEventListener('click', () => {
setIntervalFunc(intervalInfo);
setAnimationFunc(animationInfo);
});
이제 버튼을 누르면 실험이 시작된다.
시작해보자
실제로 보면 setInterval
로 설정된 애니메이션은 조금씩 버벅이는 듯한 모습을 보인다.
그 이유를 살펴보면
setInterval , requestAnimationFrame
모두 약 60FPS
를 잘 유지했지만
setInterval
의 경우에는 interval
사이의 간격의 최대값은 18ms
, 최소값은 14ms
처럼 간격이 큰 모습을 볼 수 있다.
각 프레임별 간격이 일정 할 수록 부드러운 애니메이션을 제공 할 수 있음에도 불구하고 setInterval
최대 4ms
의 간격을 보이고 있다.
그에 비해 requestAnimationFrame
의 경우에는 간격의 최대값과 최소값의 차이가 약 0.6ms
밖에 나지 않는 일관적인 모습을 보인다.
이로 인해 requestAnimationFrame
이 더 부드러운 모습을 보인다.
이처럼 간격이 일정하고 간격이 짧을 수록 부드러운 애니메이션을 제공 할 수 있다.
setInterval
에서 이렇게 프레임 사이의 간격이 차이가 났던 이유로는 다음과 같이 생각된다.setInterval
의 타이머의 간격은 1000/ 60 ms
로 매우 짧은 간격으로 점화된다. 이는 비동기적으로 이벤트 루프에서 콜스택으로 1000/ 60 ms
마다 콜백함수를 보내 호출하겠다는 것이다. 이 짧은 간격 사이에 콜스택에 다른 함수가 존재 할 때 이벤트 루프에서 콜백 함수들이 쌓이며 지연될 수 있다.
웹 브라우저의 최적화로 인해 setInterval
의 콜백 함수가 페이지의 다른 작업의 전체 성능에 영향을 주지 않고 지정된 간격보다 일찍 실행 할 수 있음을 감지하면 더 일찍 실행 하기도 한다. 이로인해 콜백 함수의 실행 타이밍이 달라졌을 수 있다.
짧은 시간동안 빠르게 함수를 호출하면서 장치의 부하가 높아지거나 리소스가 제약이 있는 경우 setInterval
콜백을 포함한 코드 실행이 영향을 받는다.
부하가 높아지면 우선순위가 높은 다른 작업부터 시행한다.
이로 인해setInterval
의 실행 타이밍이 달라질 수 있다.
가장 근본적인 이유로 setInterval
같은 경우는 렌더링을 위해 고안된 콜백 함수가 아니기 때문에 브라우저 렌더링 파이프라인 및 디스플레이 새로 고침 빈도와 동기화 되어있지 않다. 그로 인해 렌더링 유무와 상관없이 호출되기 때문에 프레임이 그려지지 않았어도 콜백함수가 호출되어 size
가 증가했을 것이다. 이로 인해 2프레임동안 1px , 1px 씩 증가했어야 하는 애니메이션이 2프레임동안 한 번만 리페인트되고 2px가 증가하는 등의 모습이 보이는 것이다.
requestAnimationFrame
에서는 프레임 사이의 간격이 일관된 이유는 다음과 같이 생각된다.requestAnimationFrame
은 콜백 함수의 실행을 브라우저의 렌더링 파이프라인 및 디스플레이 새로 고침 빈도와 동기화 되어있다. 이를 통해 최적의 시간에 렌더링 되어 원활한 성능을 보장한다.
requestAnimationFrame
은 이전 렌더링이 종료 된 후 콜백함수를 호출하도록 하여 모든 프레임들이 리페인트 될 수 있도록 최적화 한다. 이를 통해 버벅이는 모습이 나타나지 않는다.
requestAnimationFrame
은 애니메이션 작업의 우선순위를 지정함으로서 버벅거림 가능성을 최소화 하고 보다 원활한 사용자 경험을 제공한다.
requestAnimationFrame
은 근본적으로 Javascript
실행 스레드와 별개인 Composition Thread
에서 실행된다. 이러한 분리를 통해 애니메이션을 다른 Javascript
작업과 독립적으로 렌더링 하는 것이 가능하여 일관성과 성능이 향상된다.
requestAnimationFrame
의 딥다이브를 위해서는 브라우저 렌더링 파이프라인 에 대한 공부를 따로 더 해봐야겠다.
정말 운이 좋게도 찾아 볼 수 있는 포스트나 영상들이 많더라 :)
어찌저찌 하다보니
내용이 requestAnimation
에 대한 내용보다 setInterval
에 더 가까이 들어간 것 같기는 하지만 그래도 만족스럽다
optimize-scroll-event
해당 게시글을 보면requestAnimaion
이 어떻게 비동기 처리가 되는지에 대해 감을 잡을 수 있다.
requestAnimaion
도 비동기 처리지만setTimeout
과 같은 이벤트 루프가 아닌
animaion frame
이란 별도의 태스크 큐에서 처리된다.
정말 좋은 글들이 많은 블로그다. 시간 내서 천천히 읽어봐야겠다.