애니메이션은 반복적인 움직임 처리를 의미하며, CSS3의 transition과 transform 속성 및 javascript 이용해서 구현이 가능하다.
일반적으로 CSS 이용하는 것이 javascript 이용한 동작보다 빠르다.
javascript 로는 setTimeout, setInterval 이용해 구현이 가능하다. 하지만, 이벤트 콜백의 지연으로 인해 정해진 시간에 함수가 실행된다는 것을 보장할 수 없다.
이러한 지연을 브라우저에서 방지하고 가장 적절한 타이밍에 비동기 함수를 실행하는requestAnimationframe()
함수를 권장한다.
브라우저는 setTimeout, AJAX, DOM Event 과 같은 비동기 호출을 처리하기 위해 Web API 영역을 정의한다. 일반적인 함수 호출은 스택을 통해 처리가 되지만, 비동기 호출이 필요한 함수는 Web API 영역에서 비동기 기능을 수행하고 Task Queue 에 Callback 함수를 등록한다. 이후, Event Loop 는 스택이 모두 비워진 시점에 Tack Queue 에서 해당 Callback 함수를 스택에 하나씩 쌓아 처리한다.
예제를 통해 살펴보자.
function a() {
console.log(111)
}
function b() {
console.log(222)
}
function c() {
console.log(333)
}
a();
setTimeout(b, 0);
c();
출력 결과는 어떻게 될까? 정답은 111 -> 222 -> 333
순이다.
스택에 쌓이는 함수 호출 구조를 생각해보면 이해가 된다.
처음 a()
함수가 스택에 쌓이고 실행된다. 이후 setTimeout()
함수는 Timer 이벤트를 요청한 후 바로 스택에서 제거되고, c()
함수가 스택에 쌓여 호출되는 것이다. setTimeout()
함수가 실행된 후에는 Callback 함수가 Task Queue 에 등록되고, 스택에 비워지면 Event Loop 가 Callback 을 하나씩 스택에 올려 실행한다.
MDN 문서를 살펴보면 Event Loop 구조를 아래 코드와 같이 설명하였다.
while(queue.waitForMessage()){
queue.processNextMessage();
}
또한 Promise 는 조금 다르게 동작을 하는데, Promise then()
함수에 전달하는 콜백 함수는 'Task Queue' 가 아닌 'Micro Task Queue' 에 등록된다. 'Micro Task Queue' 가 'Task Queue' 보다 우선순위가 높기 때문에, Event Loop 는 Promise 통해 등록된 콜백을 먼저 처리한다. 따라서 아래의 예제를 살펴보자.
function a() {
console.log(111)
}
function b() {
console.log(222)
}
function c() {
console.log(333)
}
setTimeout(a, 0);
Promise
.resolve()
.then(b)
.then(c);
앞서 말했듯이, a()
함수가 먼저 'Task Queue' 등록됐을지라도, 'Micro Task Queue' 우선순위가 높아 Promise then()
함수로 전달한 콜백이 먼저 순차적으로 실행된다.
결과적으로, 222 -> 333 -> 111
대로 출력된다.
따라서 위에서 말한 이벤트 콜백이 지연되는 이유는 Task Queue 에서 대기하는 시간으로 인해 발생하고, setTimeout()
, setInterval()
함수를 이용해 정확한 시간 간격을 제공하지 못한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.setTimeoutBox {
background-color: aquamarine;
display: inline;
position: relative;
}
.setIntervalBox {
background-color: brown;
display: inline;
position: relative;
top: 30px;
}
</style>
</head>
<body>
<div class="setTimeoutBox">box</div>
<div class="setIntervalBox">box</div>
</body>
<script>
function animateBySetTimeout(time) {
if(time < 10) {
const box = document.querySelector('.setTimeoutBox');
box.style.left = parseInt(box.style.left || 0) + 30 + 'px';
setTimeout(animateBySetTimeout.bind(null, time + 1), 1000 / 60);
}
}
let count = 0;
function animateBySetInterval() {
const box = document.querySelector('.setIntervalBox');
box.style.left = parseInt(box.style.left || 0) + 30 + 'px';
count += 1;
}
animateBySetTimeout(0);
const interval = setInterval(animateBySetInterval, 1000 / 60);
setTimeout(() => {
clearInterval(interval);
}, 1000);
</script>
</html>
setTiemout()
, setInterval()
사용하는 경우, 다음과 같은 이슈가 발생한다.
이러한, requestAnimationFrame()
는 지연을 방지하고 가장 최적화된 타이밍에 콜백 함수를 실행하는 비동기 함수이다.
구체적으로, 브라우저가 리페인팅(HTML 문서의 전체 또는 일부 영역의 스타일이 변경되었을 때 브라우저가 변경된 스타일을 다시 적용하는 작업)이 진행되기 이전에 애니메이션을 업데이트하는 콜백 함수를 실행합니다.
기본적으로는 1초에 60번, 즉 60fps 콜백 함수를 호출하지만 모니터 주사율에 맞춰서 실행 주기가 결정됩니다. 모니터 주사율이 50fps 라면 1초에 50번 콜백 함수를 호출하는 것입니다.
setTimeout()
와 다르게, 콜백 함수는 실행 시점을 나타내는 Timestamp 값을 파라미터로 전달받아 애니메이션을 구현할 수 있으므로, 보다 제대로 된 애니메이션을 구현할 수 있습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.box {
background-color: aquamarine;
display: inline;
position: relative;
left: 30px;
}
</style>
</head>
<body>
<div class="box">box</div>
</body>
<script>
let start = null;
function animate(timestamp) {
if(!start) start = timestamp;
const progress = timestamp - start;
const box = document.querySelector('.box');
box.style.left = Math.min(progress / 10, 200) + 'px';
if(progress < 2000) {
window.requestAnimationFrame(animate);
}
}
window.requestAnimationFrame(animate);
</script>
</html>
css 이용한 애니메이션이 가장 빠르고 사용하기 간편하다는 점에서 권장된다.
css 은 transition
, transform
을 지원하여 애니메이션을 처리할 수 있다.
transition
은 transition-property
, transition-duration
두 가지 속성을 개별적으로 지정할 수 있지만, 편의를 위해 아래와 같이 한 번에 정의한다.
div {
transition: [property] [duration] [timing-function] [delay];
}
linear
, ease
, ease-in
, ease-out
, ease-in-out
값 사용)transform
은 GPU 가속을 이용하는 rotate
, translate
, skew
, scale
속성을 사용해서 element에 애니메이션을 적용할 수 있다.
div {
transition: transform 1s;
}
div:hover {
transform: translate(20px, 20px);
}
rotate
, translate
, skew
, scale
함수는 메인 스레드를 방해하지 않고 GPU에게 애니메이션 처리를 위임하여 성능을 향상시킬 수 있다.
그럼 특정 요소에 hover 시, translate 이용해 좌상단에서 우하단으로 이동하는 예제를 보자.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.box {
background-color: aquamarine;
display: inline;
position: absolute;
left: 30px;
top: 30px;
transition: transform 1s;
}
.box:hover {
transform: translate(50px, 50px);
}
</style>
</head>
<body>
<div class="box">box</div>
</body>
</html>
box 요소 위에 마우스가 올려졌을때 우하단으로 이동하는 것을 확인할 수 있다.
CSS3 표준으로 등록되기 이전까지는 각 브라우저 개발사에서 실험적으로 제공하는 기능은 '벤더 프리픽스'로 구분해 적용한다. CSS3에 표준이 되지 않은 속성은 브라우저에 따라 동작하지 않을 수 있기 때문에, 벤더 프리픽스를 사용해 적용해야 한다.
-webkit-
: Chrome, Safarims
: Internet Explorer, Microsoft Edge-o-
: Opera-moz-
: Firefox-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-webkit-flex-direction: column;
-moz-flex-direction: column;
flex-direction: column;
-ms-flex-direction: column;