본문 바로가기

FrontEnd

브라우저의 이벤트 루프와 렌더링의 관계에 대해 알아보자

반응형

브라우저의 이벤트 루프와 렌더링은 어떤 관계가 있을까요?
원문 링크 : https://xnim.me/blog/javascript-browser-event-loop-layout-paint-composite-call-stack

 

https://xnim.me/blog/javascript-browser-event-loop-layout-paint-composite-call-stack

The long journey to the runtime. Part 3. Event loop, layout, paint, composite, call stack The first 2 parts were dedicated to resource loading and application critical path. Today we will talk about the next part of the performance optimization. What is ha

xnim.me

주 : 요즘 중국인이 쓴 영어 문서, 러시아인이 쓴 영어 문서를 읽고 있는데, 새로운 언어를 공부하는 느낌입니다.
해당 문서는 러시아인이 작성했습니다.

자기 평가를 위한 문제

1. "1"이 콘솔에 찍힐까?

function loop() {
	Promise.resolve().then(loop);
}
setTimeout(() => {console.log(1)}, 0);

loop();

정답 : no
promise 태스크는 마이크로 태스크 큐로 들어감.
해당 큐의 resolved된 프로미스의 콜백이 전부 실행되기 전까지 브라우저는 렌더링을 수행하지 않음

2. 현재 화면에 링크가 있고, 해당 링크 요소에는 cursor:pointer, :hover 관련 스타일이 지정되어 있다.

아래 코드를 콘솔에서 실행한 뒤, 해당 태그와 인터랙션 하면 스타일 적용 결과를 볼 수 있을까?

while (true);

정답 : no
콜스택이 비워지기 전 까지, 브라우저는 렌더링을 수행하지 않는다.

3. 콘솔에는 로그가 어떻게 찍힐까?

Promise.resolve(1)
	.then((x) => { console.log(x); return x + 1 })
	.then((x) => {console.log(x);})

Promise.resolve(10)
	.then((x) => { console.log(x); return x * 10})
	.then((x) => {console.log(x);})

정답 : 1 10 2 100
resolved 된 순서대로 then 체인의 콜백을 하나씩 처리함

4. 팝업 요소의 높이를 0에서 auto로 애니메이션 하는 방법

https://www.geeksforgeeks.org/how-to-make-transition-height-from-0-to-auto-using-css/

 

How to make transition height from 0 to auto using CSS ? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org


우리의 목표

아래 스키마(개요)를 완전히 이해하는 것입니다.
각 단계에 대한 자세한 설명과 함께 이 스키마를 단계별로 정리할 것입니다.

이벤트 루프의 개요

이벤트 루프

과거의 운영 체제에는 아래와 유사한 로직이 있었습니다.
while (true) {
	if (execQueue.isNotEmpty()) {
		execQueue.pop().exec();
	}
}
이 코드는 모든 CPU를 사용합니다.
이전 Windows 버전에서는 그랬습니다.
최신 OS 스케줄러는 매우 복잡합니다.
  • 우선 순위 지정
  • 태스크(작업) 실행
  • 다양한 큐가 있습니다.
이벤트 루프도 이와 유사하기에,
논의를 시작하려면 실행할 태스크가 있는지 확인하는 무한 사이클이 있어야 합니다.
무한 루프
이제 우리는 어떻게든 임무를 받아야 합니다.
JS 코드를 실행하는 트리거는 무엇입니까? 
다음과 같은 것들이 될 수 있습니다.
  1. 브라우저가 <script> 태그를 처리합니다.
  2. 연기된 태스크: setTimeout, setInterval, requestIdleCallback
  3. XmlHttpRequest, fetch 등을 통한 서버 응답
  4. 브라우저 API의 이벤트 및 구독자(subscriber) 알림:
    • click, blur, input, visibilitychange, message 등
    • 그 중 일부는 사용자가 시작합니다(버튼 클릭, Alt-탭 등).
  5. promise 상태 변경.
    1. 해당 변경의 원인은 경우에 따라 js 코드 외부에 있을 수 있습니다.
  6. DOMMutationObserver, IntersectionObserver와 같은 관찰자(observer)
  7. RequestAnimationFrame
  8. 다른 무언가? WebAPI(때때로 브라우저 API라고도 함)를 통해 계획된 것들
    • 예시:
      • setTimeout(function a() {}, 100) 입력
      • WebAPI가 100ms 동안 태스크 연기
      • 100ms 후 WebAPI는 함수 a()를 대기열(TaskQueue)에 삽입
      • EventLoop는 주기에 따라 태크크를 실행합니다.

JS 코드는 어떻게든 DOM과 상호 작용해야 합니다.
요소의 크기 가져오기, 속성 추가, 일부 팝업 그리기 등을 수행하며 인터페이스를 활성화해야 합니다.
이런 특성들은 요소 렌더링에 몇 가지 제한 사항을 추가합니다.
2개의 스레드를 실행하여 그 중 하나에서 JS를 실행하고 다른 스레드에서 CSS로 렌더링하는 것은 복잡합니다.
많은 코드 동기화가 필요하기 때문입니다. 그렇지 않으면 일관성 없는 실행이 발생할 수 있습니다.
JS와 요소 렌더링이 모두 동일한 스레드에서 작동하는 이유입니다.
좋습니다. 스키마에 "렌더링"을 추가해야 한다는 의미입니다.
단일 태스크가 아니므로 별도의 대기열을 사용하는 것이 좋습니다.
그것을 렌더 큐라고 부릅시다.

이벤트 루프에 새로운 큐 추가

우리는 이제 루프에 2개의 진입점이 있습니다.
  • 하나는 대부분의 JS 작업용입니다. (SomeJSTasks라고 가칭합시다)
  • 다른 하나는 렌더링용입니다. (RenderQueue)
실제로 SomeJSTasks는 두 개의 큐로 구성되어 있습니다.
즉, 브라우저는 2개의 큐를 사용하여 대부분의 JS 코드를 실행합니다.
  • TaskQueue는 모든 이벤트, 연기된 태스크, 거의 모든 것을 위한 것입니다. 이 큐의 작업은 "태스크"입니다.
  • MicroTaskQueue는 promise 콜백(resolve / reject) 및 MutationObserver용입니다.
    • 이 대기열의 작업은 "마이크로태스크"입니다.
주 : 문맥 상 마이크로태크스인지, 태크스인지 구분할 필요가 없으면
작업(JS 코드/ 콜백)과 태스크를 마이크로태스크 / 태스크 전부에 해당하는 용어로 사용하겠습니다.

스크린(화면) 업데이트

이벤트 루프는 프레임과 불가분의 관계에 있습니다.
JS 코드를 실행할 뿐만 아니라 새 프레임을 계산합니다.
브라우저는 가능한 한 빨리 페이지의 변경 사항을 표시하려고 합니다.
몇 가지 제한 사항이 있습니다.

  • 하드웨어 제한: 화면 재생 빈도
  • 소프트웨어 제한: OS, 브라우저, 에너지 절약 설정 등

대부분의 최신 장치(및 응용 프로그램)는 60FPS(초당 프레임 수)를 지원합니다.
대부분의 브라우저는 이 특정 속도로 화면 업데이트를 시도합니다.
따라서 아티클에서는 60FPS를 기준으로 사용하지만 명확한 속도는 다를 수 있음을 염두에 두는 것이 좋습니다.
(주 : 즉, 하나의 태스크를 16.6ms 내에 처리하는 것을 목표로 가정)


태스크 큐란 무엇인가

TaskQueue에 태크스가 들어오는 즉시, 해당 큐에서 우선순위가 가장 높은 태크스를 가져와 각 싸이클(이벤트 루프 쓰레드)에서 실행합니다.
실행 후 시간이 충분하면(즉, 렌더 큐에 태스크가 들어오지 않으면)
또 다른 태스크를 싸이클로 가져오고, 렌더 큐가 작업을 받을 때까지 계속 태스크 큐의 작업을 처리합니다.
 
태스크 큐 처리
A, B, C의 3가지 작업이 있습니다.
  • 이벤트 루프는 첫 번째 작업 A를 가져와서 실행합니다. 이는 4ms가 걸립니다.
  • 그런 다음 이벤트 루프는 다른 대기열(MicroTaskQueue 및 Render Queue)을 확인합니다.
    • 두 큐는 지금 비어 있다 가정합시다. (16.6ms안지남, 프로미스 없음)
    • 이벤트 루프가 두 번째 작업을 실행하는 이유입니다.
  • 그 다음 작업 B 실행에는12ms가 걸립니다.
    • 즉, 총 2개의 작업 처리에 우리는 16ms를 사용합니다.
  • 브라우저는 렌더링 대기열에 작업을 추가하여 새 프레임을 그립니다.
    • 60fps를 위해 약 16ms마다 프레임을 업데이트 해야 합니다.
  • 이벤트 루프는 렌더링 대기열을 확인하고 이러한 작업의 실행을 시작합니다. 약1ms가 걸립니다.
  • 이러한 작업 후에 이벤트 루프는 다시 TaskQueue로 돌아갑니다.
이벤트 루프는 작업이 얼마나 오래 실행될지 예측할 수 없습니다.
또한 이벤트 루프는 프레임을 렌더링하기 위해 작업을 일시 중지할 수 없습니다.
 
이는 브라우저 엔진이
사용자 JS 코드에서 변경 사항을 렌더링 할 수 있는지,
즉 현재 코드의 실행 상태가 UI에 반영을 위한 최종 상태인지 일종의 준비 단계인지 알 수 없기 때문입니다.
이를 위한 API는 없습니다.
 
즉, JS 코드 실행 중에 JS가 수행한 모든 변경 사항은 사용자에게 렌더링된 프레임으로 표시되지는 않지만,
우리는 코드 실행이 얼마나 걸리는지 계산할 수는 있습니다.
 
이제 두 번째 예를 살펴보겠습니다.
두 개의 태스크가 존재하는 태스크 큐
 
대기열에 작업이 2개 있습니다.
첫 번째는 꽤 길며 240ms가 걸립니다.
60FPS는 각 프레임이 16.6ms마다 렌더링되어야 함을 의미하므로 약 14프레임이 손실됩니다.
따라서 작업이 종료되자마자 이벤트 루프는 렌더링 큐의 작업을 실행하여 프레임을 그립니다.
중요 참고 사항: 14개 프레임 손실은 브라우저가 연속으로 15개 프레임을 렌더링한다는 의미가 아닙니다.

MicroTaskQueie를 검토하기 전에 콜 스택에 대해 이야기해 봅시다.

콜 스택

콜 스택은 현재 호출 중인 함수와 현재 함수 실행이 완료될 때 함수 간 전환이 발생하는 위치를 보여주는 목록입니다.
예를 살펴보겠습니다.

function findJinny() {
  debugger;
  console.log('It seems you get confused with universe');
}

function goToTheCave() {
  findJinny();
}
function becomeAPrince() {
  goToTheCave();  
}
function findAFriend() {
   // ¯\_(ツ)_/¯
}
function startDndGame() {
	const friends = [];
  while (friends.length < 2) {
    friends.push(findAFriend());
  }
  becomeAPrince();
}
console.log(startDndGame());

브라우저 콘솔에서 이 코드를 실행하고
디버거 명령에서 일시 중지합니다.
콜 스택은 어떻게 표시될까요?

인라인 코드에서 스택을 시작합니다: console.log(startDndGame());
이는 호출 스택의 시작입니다.
그런 다음 startDndGame 함수로 내려가 findAFriend를 여러 번 호출합니다.
이 함수는 디버거에 도달하기 전에 종료되므로 호출 스택에 표시되지 않습니다.
 
콜스택 모양

이는 콜 스택이 동작하는 방식입니다.

현재 실행 중인 모든 함수의 큐(스택)이며

콜 스택은 현재 함수가 종료된 후 올바른 위치로 돌아가도록 도와줍니다.


마이크로태스크 큐란 무엇인가?

마이크로태스크는 구체적입니다.
Promise 또는 MutationObserver 콜백일 수 있습니다.
Microtasks는 꼼수의 일종이며, 일반 태스크과 비교할 때 몇 가지 장단점을 부여합니다.

마이크로태스크의 주요 기능은 콜 스택이 비는 즉시 실행된다는 것입니다.
예를 들어 다음과 같은 콜 스택이 있을 수 있습니다.

콜 스택 revisited
resolve 또는 rejected 상태의 promise가 있는 경우 스택의 모든 요소가 종료되는 즉시 실행됩니다.
모든 js 코드는 콜 스택(논리적 개념임)에 등록됩니다.
즉, 콜 스택의 종료는 태스크 또는 마이크로태스크의 종료입니다.
여기에 흥미로운 사실이 있습니다.
마이크로태스크는 콜스택이 종료될 때 실행할 새로운 마이크로태스크를 생성할 수 있습니다.
즉 마이크로태크스가 종료하면서 새로운 마이크로태스크를 생성할 수 있습니다.
즉, 페이지 렌더링이 영원히 연기될 수 있습니다. 이것이 마이크로태스크의 주요 "기능"입니다.

 

마이크로태스크는 렌더링의 무한 연기가 가능
 
MicrotaskQueue에 4개의 마이크로태스크가 있는 경우 차례로 실행됩니다.
렌더링은 몇 초가 소요될 수 있지만, 반드시 이 4개의 마이크로 태스크 처리 후에만 실행됩니다.
이 모든 시간 동안 사용자는 웹 사이트와 인터랙션 할 수 없습니다.
 
이 마이크로태스크의 특징은 장점이자 단점이 될 수 있습니다.
예를 들어 DOM이 변경될 때 MutationObserver가 콜백을 호출하면
사용자는 콜백이 완료되기 전에 페이지에서 변경 사항을 볼 수 없습니다.
이를 통해 사용자가 보게 될 콘텐츠를 효과적으로 관리할 수 있습니다.
지금까지의 설명을 개요에 반영

우리는 RenderQueue에 렌더링 작업이 없는 경우

  • 태스크(not 마이크로태스크가)가 연속적으로 처리될 수 있음을 알았습니다.
  • 마이크로태스크 큐가 비어있기 전까지는 렌더링 작업을 처리할 수 없음을 알았습니다.

RenderQueue 내에선 어떤 것이 실행되나요?

각 프레임 렌더링은 여러 단계로 나눌 수 있습니다.
또한 각 단계는 하위 단계로 나눌 수 있습니다.

프레임 렌더링 단계

각 단계에 대해 자세히 살펴 보겠습니다.

RequestAnimationFrame (raf)

RequestAnimationFrame (raf)

브라우저는 렌더링을 시작할 준비가 되었습니다.
우리는 해당 단계를 구독하고 애니메이션 단계를 위한 프레임을 계산하거나 준비할 수 있습니다.
이 콜백은 프레임 렌더링 직전의 애니메이션 작업이나 DOM의 일부 변경 계획에 적합합니다.

몇 가지 흥미로운 사실:
  • Raf의 콜백에는 다음과 같은 인수가 있습니다: DOMHighResTimeStamp
    • "time origin"(문서 수명의 시작) 이후 경과된 밀리초 수입니다.
    • 따라서 콜백 내에서 performance.now()를 사용할 필요가 없을 수도 있습니다.
  • raf는 descriptor (id)를 반환하므로 cancelAnimationFrame을 사용하여 raf를 취소할 수 있습니다. (setTimeout 처럼);
  • 사용자가 탭을 변경하거나 브라우저를 최소화하면 다시 렌더링되지 않으므로 raf도 사용할 수 없습니다.
  • 요소의 크기를 변경하거나 요소 속성을 읽는 Js 코드는 requestAnimationFrame을 강제할 수 있습니다.

브라우저가 프레임을 렌더링하는 빈도를 확인하는 방법? 다음 코드가 도움이 될 것입니다.

const checkRequestAnimationDiff = () => {
	let prev;
	function call() {
		requestAnimationFrame((timestamp) => {
			if (prev) {
				console.log(timestamp - prev); // It should be around 16.6 ms for 60FPS
			}
			prev = timestamp;
			call();
		});
	}
	call();
}
checkRequestAnimationDiff();

checkRequestAnimationDiff 실행 결과

Style (recalculation)

브라우저는 적용해야 하는 스타일을 다시 계산합니다. 이 단계는 또한 활성화할 미디어 쿼리를 계산합니다.

style 단계

스타일 재계산에는
직접 변경 (a.styles.left = '10px' 및 element.classList.add('my-styles-class')) 와 같이
CSS 파일을 통해 설명된 변경이 모두 포함됩니다.
CSSOM 및 렌더 트리 모두에서 재계산됩니다.

프로파일러를 통해 스타일에 걸린 시간을 알 수 있음

Layout

레이어 계산. 
즉, 요소 위치, 크기 및 서로에 대한 상호 영향을 계산합니다.
페이지에 DOM 요소가 많을수록 작업이 더 어려워집니다.
각 작업에 시간이 어떻게 소비되는지 이해를 돕기 위해 일부 브라우저는 프로세스를 일부 하위 프로세스로 나눕니다.
  • 예를 들어 Chrome에서는 update layer tree / layout shift를 볼 수 있습니다.
layout Shift는 요소를 서로 상대적으로 이동하는 역할을 합니다.

레이아웃 단계

레이아웃은 모던 웹 사이트에서 상당히 고통스러운 작업입니다.
레이아웃은 다음과 같은 경우에 발생합니다.
  • 요소의 크기 및 위치와 관련된 속성 읽기(offsetWidth, offsetLeft, getBoundingClientRect 등)
  • 요소 중 일부(transform 및 will-change 등)를 제외한 요소의 크기 및 위치와 관련된 속성을 작성합니다.
레이아웃은 다음을 담당합니다.
  • 레이아웃 계산
  • 레이어에 요소 삽입
레이아웃(raf 또는 style 단계 포함 여부와 무관하게)은 페이지를 렌더링하고 변경 사항을 적용하려는 경우 외에도
js가 요소의 크기를 조정하거나 속성을 읽을 때 발생할 수 있습니다.
이 프로세스를 강제 레이아웃(force layout)이라고 합니다.
  • 레이아웃을 강제하는 속성의 전체 목록: https://gist.github.com/paulirish/5d52fb081b3570c81e3a.
  • 중요 참고 사항: 레이아웃이 강제 설정되면 콜 스택이 비어 있지 않아도 브라우저가 기본 스레드에서 JS 실행을 일시 중지합니다.

예제에서 확인해 봅시다:

div1.style.height = "200px"; // Change element size
var height1 = div1.clientHeight; // Read property​
 
브라우저는 실제 크기를 다시 계산하지 않고는 div1의 clientHeight를 계산할 수 없습니다.
이 경우 브라우저는 JS 실행을 일시 중지하고 Style(변경 사항 확인), Layout(크기 재계산)을 실행합니다.
Layout은 div1 앞에 배치된 요소뿐만 아니라 뒤에 배치된 요소도 계산합니다.
최신 브라우저는 매번 전체 dom 트리를 다시 계산하지 않도록 계산을 최적화하지만 아닌 경우도 있습니다.
재계산 프로세스를 레이아웃 이동(layout shift)이라고 합니다.
스크린샷에서 확인하고 레이아웃 중에 수정되고 이동될 요소 목록이 있는지 확인할 수 있습니다.
레이아웃 이동
브라우저는 매번 레이아웃을 강제하지 않으려고 합니다.
따라서 그들은 작업을 그룹화합니다.
div1.style.height = "200px";
var height1 = div1.clientHeight; // <-- layout 1
div2.style.margin = "300px";
var height2 = div2.clientHeight; // <-- layout 2 
첫 번째 줄 브라우저에서 높이가 변경되었습니다.
두 번째 줄에서 브라우저는 속성 읽기 요청을 받았습니다.
보류 중인 높이 변경이 있으므로 브라우저는 강제 레이아웃을 수행해야 합니다.
3번째 줄과 4번째 줄에 있는 것과 같은 상황입니다.
브라우저에 더 적합하도록 읽기 및 쓰기 작업을 그룹화할 수 있습니다.
div1.style.height = "200px";
div2.style.margin = "300px";
var height1 = div1.clientHeight; // <-- layout 1
var height2 = div2.clientHeight;​

요소를 그룹화하면, 브라우저가 4번째 줄에 도달할 때 이미 필요한 레이아웃이 수행되었으므로 두번째 레이아웃을 수행할 필요가 없습니다.
따라서 이벤트 루프는 태스크 처리 및 마이크로 태스크 처리 단계 모두에서 레이아웃을 강제할 수 있으므로
최종 화면 렌더링을 위한 루프는 한 번의 싸이클이 아니라 여러 번의 부분 싸이클이 될 수도 있습니다.

  • 태스크 처리 도중 강제 레이아웃 때문에 렌더 큐 처리로 갔다가 다시 태스크 큐 처리로 돌아올 수 있다.
  • raf 콜백으로 강제 레이아웃을 유발하는 오퍼레이션을 배치 처리할 수 있다.
    • 해당 콜백에서 읽기와 쓰기를 배치 처리하는 경우, 스타일 재계산과 레이아웃을 한번씩만 수행하는 것이 가능하다.

변경된 개요. 태스크 처리 도중 강제로 렌더 큐 처리로 갔다가 다시 태스크 큐 처리로 돌아올 수 있다.

레이아웃을 최적화하는 방법에 대한 몇 가지 조언:
  • DOM 노드 수 줄이기
  • 불필요한 레이아웃을 제거하기 위한 dom 속성 읽기 작업 / 쓰기 작업 별 그룹화
  • 레이아웃을 강제하는 작업을 합성(composition)을 강제하는 작업으로 교체

Paint

paint
뷰포트의 요소에는 각각 위치 및 크기가 있습니다.
이제 색상, 배경을 적용해야 합니다.
paint 단계

이 작업은 일반적으로 많은 시간을 소비하지 않지만 첫 번째 렌더링 시 클 수 있습니다.
이 단계 후에 프레임을 "물리적으로" 그려야 합니다.
마지막 작업은 "합성(composition)"입니다.

Composition

composition 단계

composition은 GPU에서 실행되는 유일한 단계입니다.
이 단계에서 브라우저는 "transform"과 같은 특정 CSS 스타일만 실행합니다.

중요 사항: transform: translate는 GPU 렌더링 모드를 on 하는 것이 아닙니다.
컴포지션을 위한 연산을 GPU에 할당하는 것 뿐입니다.
따라서 GPU에 렌더링 작업 처리를 맞기기 위해 코드베이스에 transform: translateZ(0)을 사용하는 경우
이러한 방식으로 동작하지 않습니다.
그것은 잘못된 생각입니다.

최신 브라우저는 연산의 일부를 자체적으로 GPU로 옮길 수 있습니다.
이에 해당하는 연산에 대한 최신 목록을 찾지 못했으므로 소스 코드를 확인하는 것이 좋습니다.
 

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.cc;l=39

 

source.chromium.org

Composition
transform은 복잡한 애니메이션에 가장 적합한 선택입니다.
  1. 매 프레임마다 강제 레이아웃을 수행하지 않고 CPU 시간을 절약합니다.
  2. 이 애니메이션은 웹 사이트에 top, right, bottom, left을 통해 구현한 애니메이션이 있을 때 따를 수 있는 작은 지연을 제거합니다.

어떻게 렌더링을 최적화 하나요?

프레임 렌더링에서 가장 어려운 작업은 레이아웃입니다.
복잡한 애니메이션이 있는 경우 렌더링할 때마다 13-20ms(또는 그 이상)를 소비하며,
겉보기엔 드러나는 효과가 없는 모든 DOM 요소를 이동해야 할 수 있습니다.
프레임이 손실되어 웹 사이트 성능이 저하됩니다.
 
색상, 배경 이미지 등을 변경하면 레이아웃 단계를 피할 수 있습니다.
레이아웃 단계 통과

transition을 사용하고 DOM 요소에서 속성을 읽지 않으면 레이아웃과 페인트가 필요하지 않을 수 있습니다.

레이아웃 / 페인트 통과

요약

  1. 애니메이션을 JS에서 CSS로 이동합니다. 추가 JS 코드 실행은 "무료"가 아닙니다.
  2. "움직이는" 객체 애니메이션 : transform 사용
  3. will-change 속성을 사용합니다. 이를 통해 브라우저는 DOM 요소의 transform 적용을 위한 준비를 수행할 수 있습니다.
  4. DOM 변경 배치 처리
  5. requestAnimationFrame을 사용하여 다음 프레임에 반영할 변경 사항을 계산합니다.
    1. 어차피 해야 하는거 같이 처리? 
  6. css 읽기 연산과 쓰기 연산을 각각 모아 처리합니다. 메모이제이션을 사용합니다.
  7. 레이아웃을 강제하는 속성에 주의하세요: https://gist.github.com/paulirish/5d52fb081b3570c81e3a 
  8. 사소한 상황이 아닌 경우 프로파일러를 실행하여 렌더링 빈도와 타이밍을 확인하는 것이 좋습니다.
  9. 단계별로 최적화하고 한 번에 모든 작업을 수행하려고 하지 마십시오.

최종 개요


참고

https://www.kirupa.com/html5/animating_with_requestAnimationFrame.htm

 

Animating with requestAnimationFrame

Learn how to create silky smooth animations by using requestAnimationFrame instead of a timer-based approach like setInterval or setTimeOut.

www.kirupa.com

 



반응형