티스토리 뷰
사람은 1초에 60개의 프레임을 볼 수 있다고 한다. 그 이상의 프레임을 더 찍어내더라도 사람이 느끼기엔 거의 차이가 없다는 말이다. 그래서 자바스크립트로 애니메이션을 구현할때도 1초에 60프레임정도를 찍어내면 된다. 그 말은, 1프레임을 찍어내는데 16.6ms(1000ms / 60frame)를 넘겨서는 안된다는 말이다.
프레임이란 그냥 한장의 사진이라고 생각하면 된다. 동영상도 사실 여러장의 사진이 빠르게 움직여서 움직이는것처럼 보이는것일 뿐이다.
16.6ms마다 프레임을 찍어내기 위해 첫번째로 사용할 수 있는 방법은 setInterval이다.
setInterval(function() {
// animiate something
}, 1000/60);
또 다른 방법은 requestAnimationFrame이다.
setInterval보다 requestAnimationFrame이 더 좋은 이유
브라우저가 프레임 생성 초기 단계에 맞춰 애니메이션 코드를 실행시킴으로써 애니메이션이 더 부드럽게 동작한다. setInterval이나 setTimeout은 프레임을 신경쓰지 않고 동작한다. 만약에 애니메이션 코드가 엄청 복잡해서 실행하는데 50ms가 걸린다고 해보자. 그럼 16.6ms동안 프레임을 찍어내지 못했기 때문에 화면이 뚝뚝 끊기는듯한 현상이 발생한다.
setTimeout이 실행되서 애니메이션 자바스크립트가 실행되었다. 근데 프레임 시작 시간에 맞춰 실행된게 아니라 중간쯤에서 실행이 시작되었다. 그래서 프레임 시간 16.6ms를 초과해버렸다. 또한 자바스크립트 실행에 의해서 리플로우가 일어나서 레이아웃 - 페인트 - 합성 과정이 다시 일어났다. 프레임이 생성되지 못하고 누락되어 버렸다.
자바스크립트는 싱글스레드 이기 때문에, 한번에 하나의 작업만 할 수 있다. 무거운 작업을 자바스크립트로 하게 되면 프레임이 누락될 가능성이 높아진다. 그 자바스크립트를 실행하느라 화면을 제때 렌더링 하지 못하기 때문이다. 예를들어, 스크롤 이벤트에 이벤트 핸들러를 달아서 어떤 효과를 줬다고 해보자. 이 이벤트 핸들러가 20ms동안 실행될 경우 그 자바스크립트가 끝까지 실행될떄까지 브라우저는 화면을 다시 그리지 못한다. 결국 스크롤이 부드럽지 못하고 뚝뚝 끊기는 프레임 누락 현상이 발생한다.
결론은, 빈번하게 호출되는 이벤트 핸들러에는 보통 3~4ms 정도로 실행을 마치게끔 해야한다. 그래야, 자바스크립트 실행 이후 리플로우 과정까지 총 16ms내에 프레임을 찍어낼 수 있게 된다.
DOM과 관련되지 않은 자바스크립트의 실행을 Web Workers로 이전해서 별도의 쓰레드에서 실행하게 하면 프레임 누락 현상을 방지할 수 있다고 하는데 아직 사용해본적은 없다. 대신이 Web Workers에서는 DOM에 접근하지 못한다.
어쩔수없이 3~4ms를 넘기는 애니메이션은 어떻게 합니까?
그런 경우엔, 큰 작업을 작은 작업으로 나눠서 실행하면 된다. 중요한건 절대 하나의 이벤트 핸들러 실행이 3~4ms를 넘겨선 안된다는것이다. 또한 setInterval이나 setTimeout보다는 requestAnimationFrame을 사용해서 프레임 생성 시작시간에 맞춰 애니메이션 코드를 실행하자.
가장 간단한 형태의 사용법
(function repeatOften() {
// 애니메이션 실행 코드
requestAnimationFrame(repeatOften);
})();
이렇게 재귀 형식으로 구현하면 브라우저가 16.6ms 만에 프레임을 찍어낼 여유가 생겼을때마다 애니메이션 코드가 실행된다.
requestAnimationFrame 취소하기
setInterval이나 setTimeout이 id를 리턴해서 그 id를 통해 취소할 수 있는것처럼 requestAnimationFrame도 id를 통해 취소시킬 수 있다.
var globalID;
function repeatOften() {
$("<div />").appendTo("body");
globalID = requestAnimationFrame(repeatOften);
}
$("#start").on("click", function() {
globalID = requestAnimationFrame(repeatOften);
});
$("#stop").on("click", function() {
cancelAnimationFrame(globalID);
});
이런식으로 실행시키면 start버튼을 눌렀을때 repeatOften애니메이션 함수가 프레임에 단위에 맞춰 재귀적으로 반복 호출 된다. 또한 stop버튼으로 취소 시킬 수 있다.
브라우저 지원
보시다 시피 97.52%의 기기를 커버할만큼 지원이 좋은편이지만 역시나 우리의 IE에서는 10버전 부터 지원된다. 안드로이드도 4.4까지 지원되는데 IE는 정말 열악하다.
하지만 우리에겐 polyfill이 있기 떄문에 괜찮다!
Polyfill (출처)
(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);
};
}());
polyfill은 이렇게 생겼다. 보시다시피, 브라우저에 requestAnimationFrame이라는 메소드가 없으면 setTimeout을 통해서 callback(애니메이션 함수)를 호출하게끔 구성되어있다.
출처
https://css-tricks.com/using-requestanimationframe/
'Javascript' 카테고리의 다른 글
Promise와 콜백 패턴의 가장 큰 차이 (0) | 2020.05.14 |
---|---|
es6의 Generator와 es7의 Async/Await (0) | 2020.05.14 |
ES6에 새로 추가된 Primitive Type - Symbol 타입 (0) | 2020.03.27 |
#20-1 참조 투명성 예시 (referential transparency) (0) | 2019.09.26 |
#20 순수함수와 사이드 이펙트 (0) | 2019.09.26 |
- Total
- Today
- Yesterday
- useEffect
- rendering scope
- react
- es6
- react hooks
- server side rendering
- storybook
- reducer
- Babel
- promise
- reactdom
- javascript
- reflow
- props
- Polyfill
- mobx
- webpack
- type alias
- await
- return type
- Action
- typescript
- hydrate
- state
- atomic design
- design system
- async
- computed
- useRef
- Next.js
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |