50. 자바스크립트 애니메이션

소개

이번 글에서 자바스크립트를 이용해 애니메이션을 만드는 작업을 알아 볼 것이다 — 애니메이션은 사용자경험을 추가하기 위해 종종 사용되는데 애니메이션이 지원되는 브라우저를 이용하는 사용자들을 위한 것이다. 부드럽게 확장되거나 접히는 패널, 진행바, 폼에서의 시각적인 피드백 같은 것에 일반적으로 사용된다.

카툰이나 플릭북1을 봤던 사람은 알고 있듯이, 애니메이션은 무언가를 움직이는 것처럼 만들기 위해 수많은 작은 단계들로 되어 있다. 애니메이션은 강력한 기술이다; 주의를 사로잡는데 좋다. 여기서 흠이라면 당신이 원하든 원하지 않던 간에 주의를 사로잡는다는 것이다. 애니메이션된 효과들은 웹애플리케이션이 좀 더 일관된 경험을 느끼도록 만들 수 있지만, 그것은 마치 핫 칠리와 같다: 너무 많이 추가할 수는 없다.

이 글의 내용은 다음과 같다:

  • 간단한 예제: 옐로 페이드(yellow fade) 기법
  • 자바스크립트 라이브러리를 이용한 애니메이션
  • 더 복잡한 예제: 이동하고 크기를 변경하기
  • CSS 전이(CSS Transitions)
  • 요약
  • 연습 문제

간단한 예제: 옐로 페이드 기법

일반적으로 사용하는 애니메이션의 하나는 옐로 페이드 기법으로 페이지에서 변경된 영역이 노란바탕색이 되었다가 희미해지면서 원래 바탕색으로 돌아오는 것이다. 이것은 사용자가 하고 있는 것을 방해하지 않고 변경된 무언가가(예를 들어 내용이 더 나온다거나 어떤 폼이 메세지를 피드백하는 경우) 하이라이트되도록 하는데 적절하고 겸손한 방법이다. 어떻게 보이는지 확인하기 위해 옐로 페이드 예제를 보라.

페이드 원리는 희미해지도록 할 요소의 바탕색을 노란색으로 설정한 후 연속된 단계로 원래색으로 되돌리는 것이다. 그래서 원래 바탕색이 빨간색이었다면, 바탕색을 노란색으로 설정한 후 오렌지-옐로로, 오렌지로, 레드-오렌지로 그리고 레드로 계속 바꾼다. 당신이 취하는 단계의 수는 색 변화가 얼마나 부드럽게 할 것인가를 정하게 되며 단계 사이의 시간은 전체 색변화가 얼마나 오래걸릴 것인가를 정하게 된다. 색을 변화시키는데 우리는 유용한 CSS 정보를 이용할 수 있다: 색은 16진수 문자열 뿐아니라 3개의 순열로 정의될 수 있다. 그래서 #FF0000 (레드) 는 또한 rgb(255,0,0)로 지정될 수 있다. 5단계로 노란색에서 빨간색으로 바뀌는 것은 rgb(255,255,0)(옐로우)에서 rgb(255,0,0)로 다음과 같은 단계로 바뀌는 것을 의미한다:

rgb(255,255,0)
rgb(255,192,0)
rgb(255,128,0)
rgb(255,64,0)
rgb(255,0,0)

바탕색을 rgb(255,255,0)로 설정하고, 일정 시간(예를 들어 100밀리초) 후에 rgb(255,192,0)로 바탕색을 바꾸고 또 100ms 후에 rgb(255,128,0)로 바꾸고 등등을 한다:

Colour Time
rgb(255,255,0) 0
rgb(255,192,0) 100ms
rgb(255,128,0) 200ms
rgb(255,64,0) 300ms
rgb(255,0,0) 400ms

전체 과정은 400ms(단 0.5초도 안되는 시간)가 걸려서 노란색에서 빨간색으로 부드럽게 페이드 된다. 간단하게 여기서 우리는 단지 색의 일부만 변경하고 있지만 (초록색채널: rgb 색상의 3 부분은 빨간색, 초록색, 파란색 채널이다), 한번에 하나 이상의 채널을 변경이 가능하다. 이 예제에서 당신은 4단계로 초록색채널을 255에서 0으로 변경하고 있는데 이는 각 단계마다 64씩 변경됨을 의미한다.

특정 시간 후에 동작이 일어나도록 하는 것은 자바스크립트에서 setTimeoutsetInterval 함수들을 이용한다. setTimeout 함수는 특정 시간이 지난 후에 한번 동작을 수행한다; setInterval 함수는 시간차로 나눠진 각각의 시간에 계속 반복해서 동작을 수행한다; 이것이 애니메이션의 전형이다. 본질적으로 이 페이드를 동작시키는 방법은 각 단계에 무엇을 수행를 정하고 setInterval 을 이용해 이것을 반복해서 호출하는 것이다. setInterval 함수는 2개의 매개변수를 받는다: 동작을 호출할 함수와 밀리초 단위의 시간차 값이다.

분명히 당신은 항상 노란색에서 빨간색으로만 변하기를 원하지 않으므로 함수는 일반화가 필요하다. 만일 당신이 시작과 끝나는 색상을 알고 있고 단계의 수를 알고 있다면 각 단계에서 각 색상이 얼마만큼 변하도록 할지는 수학적인 문제이다. 당신이 3개의 숫자로 이루어진 startcolour 배열([255,255,0])과 endcolour 배열([255,0,0])을 정의하면 각 단계에 각 색상이 변하는 양은 다음과 같다:

var red_change = (startcolour[0] - endcolour[0]) / steps;
var green_change = (startcolour[1] - endcolour[1]) / steps;
var blue_change = (startcolour[2] - endcolour[2]) / steps;

그래서 단계마다 요소의 바탕색을 변경하기 위해 setInterval을 이용하면 다음과 같이 된다:

var currentcolour = [255,255,0];
var timer = setInterval(function(){
    currentcolour[0] = parseInt(currentcolour[0] - red_change);
    currentcolour[1] = parseInt(currentcolour[1] - green_change);
    currentcolour[2] = parseInt(currentcolour[2] - blue_change);
    element.style.backgroundColor = 'rgb(' + currentcolour.toString() + ')';
}, 50);

각 단계에서 currentcolour를 취하여 빨간색채널을 red_change 양만큼, 초록색채널을 green_change 양만큼, 파란색채널을 blue_change 양만큼 변경한다. 그리고 요소의 실제 바탕색을 새로운 색상으로 설정한다: [255,255,0].toString() 은 “255,255,0” 가 되므로 rgb(255,255,0) 색상을 가져오기 위해 toString() 이용하고 요소의 바탕색으로 설정한다.

그러나, setInterval은 당신의 수행 함수를 무한히 호출할 것이다; 목표 색상에 도달했을 때 멈추기 않을 것이다. interval을 멈추기 위해서 clearInterval()을 이용한다; 다음 코드는 동작이 호출된 수를 계산해서 정확한 단계 숫자 후에 interval 을 지워준다.

var currentcolour = startcolour;
var stepcount = 0;
var timer = setInterval(function(){
    currentcolour[0] = parseInt(currentcolour[0] - red_change);
    currentcolour[1] = parseInt(currentcolour[1] - green_change);
    currentcolour[2] = parseInt(currentcolour[2] - blue_change);
    element.style.backgroundColor = 'rgb(' + currentcolour.toString() + ')';
    stepcount += 1;
    if (stepcount >= steps) {
        element.style.backgroundColor = 'rgb(' + endcolour.toString() + ')';
        clearInterval(timer);
    }
}, 50);

자 이제 애니메이션을 적용하는 방법이다: 단번에 원스텝으로

startcolourendcolour 는 어떻게 가져와서 설정할까? 한가지 간단한 방법은 fade 라는 함수에 위의 코드를 감싸주는 것이다:

fade: function(element, startcolour, endcolour, time_elapsed) {
   ...code from above...
}

그리고 다음과 같이 호출을 하면 요소에 옐로우 페이드를 동작하도록 할 수 있다:

fade(document.getElementById("yft"), [255,255,60], [0,0,255], 750);

혹은 (요소의 바탕색을) 빨간색으로 설정하고 파란색으로 페이드하도록 하는 “레드 페이드”를 쓸수도 있다:

fade(document.getElementById("yft"), [255,0,0], [0,0,255], 750);

이 예제는 바탕색을 변경하는 것이지만 변형된 어떤 것이든 할 수 있을 것이다: (눈을 현혹시키는 1960년대 환각 문자 효과를 위한) 전경색, (물체를 사라졌다 나타났다하도록 하기 위한) 불투명도, (요소를 페이지 주변에서 이동하도록 하기 위한) 위치, (요소를 커지게 하거나 사라지기 전에 작아지도록 하기 위한) 높이와 너비

자바스크립트 라이브러리를 이용한 애니메이션

애니메이션은 일반적으로 사용되는 효과라서 대부분의 자바스크립트 라이브러리들은 일반적인 애니메이션을 위한 내장 지원을 포함함으로써, 애니메이션을 어느 정도까지는 지원한다. 예를 들어 jQuery는 엘리먼트를 희미해지면서 사라지도록 만들기 위한 내장 지원을 갖는다:

$("#myelement").fadeOut();

그리고 더 복잡한 맞춤 작업을 위한 animate() 함수가 있다:

$("#block").animate({
    width: "70%",
}, 1500 );

이것은 상당히 직관적이다 – 엘리먼트를 찾아서 CSS width 속성을 1500 밀리초 동안 지금 상태에서 70% 로 변경시킨다. – animate 함수는 여기에 문서화 되어 있다.

Prototype기반 scriptaculous 프레임웍Effect.Fade('id_of_element') 처럼 비슷한 기능과 많은 다른 기능들을 제공한다. Yahoo UI 라이브러리 또한 비슷한 효과들을 만들어낼 수 있다:

new Y.Anim({ node: '#demo', to: { width: 70%, }}).run();

당신이 이미 코드에서 자바스크립트 라이브러리를 사용하고 있다면, 당신이 직접 setInterval 함수로 애니메이션들을 관리하는 것보다 그 라이브러리들이 훨씬 더 간단한 애니메이션 기능들을 제공하고 있다는 것을 이미 알고 있을 것이다. 그러나 내가 생각하기에 내부에서 무엇을 하고 있는지 이해하는 것은 중요하다 – 그것이 궁극적으로는 당신의 스크립팅 기술을 더 튼튼하게 만들 것이다. 이것이 내가 라이브러리를 시작하기 전에 근본 원리로 부터 예제를 경험하도록 한 이유이다.

dev.opera.com 의 자바스크립트 툴킷 소개에서 여러 자바스크립트 라이브러리들을 사용하는 것에 대해 더 많은 자료들을 찾을 수 있다. 

더 복잡한 예제: 이동하고 크기를 변경하기

옐로 페이드 기술로 애니메이션을 설명하는 동안 좀 지루했을 것이다. 대부분 사람들은 애니메이션을 떠올릴때 움직임 을 연상한다. Wile E. Coyote 2 가 지금까지 색상만 변경하는 것이 전부였다면 별로 재밌지 않았을 것이다.

사용자에게 작업흐름을 끊기지 않고 그들에게 무언가를 알려주기 위한 좋은 수법은 non-modal message 이다. 사용자가 OK를 클릭해야만 하는 alert() 다이얼로그를 띄워주는 대신에, 사용자가 원할 때까지 겸손하게 페이지에서 떠있는 div에 간단하게 메시지를 넣어주는 것이다. 그리고 좀 더 좋게 하자면 사용자가 확인한 메시지를 다시 꺼내어 읽어 볼 수 있도록 하는 것이다. 자 이제 떠있는 메세지를 구현해보자, 이 메세지는 클릭하면 화면 구석으로 축소되고 다시 클릭하여 꺼내어 볼 수 있다. 이해를 위해 “zooming message”의 간단한 데모 에서 확인할 수 있다.

만약 중대한 애니메이션 작업이나 중대한 자바스크립트 작업을 하고 있다면 거의 언제나 자바스크립트 라이브러리를 사용할 가치가 있을 것이다. 이는 당신이 애니메이션들이 동작하는데 필요한 계산의 핵심에 대해 걱정할 필요없이 원하는 사용자 경험을 제공하는데 도움을 줄 것이다. (당신은 이제 위의 첫번째 예제를 통해 어떻게 계산해야 하고 setInterval을 어떻게 사용해야할지 알고 있지만 시간을 절약하고 당신의 뇌세포를 힘겹게 하지 않고 다른 이를 위해 남겨둘 수 있다.)

위의 데모는 jQuery 라이브러리를 사용해서 동작하지만, 앞서 언급한바와 같이 대부분의 라이브러리들은 거의 비슷한 개념으로 애니메이션을 제공해서 당신이 선호하는 라이브러리를 이용해 재구현할 수 있을 것이다. 본질적으로 우리는 아래와 같은 것을 원한다:

  1. 화면의 중앙에 떠있는 메세지를 보여준다
  2. 클릭했을때:
    1. 오른쪽 끝으로 수평이동
    2. 상단으로 수직이동
    3. 20px 너비로 변경
    4. 20px 높이로 변경
    5. 불투명도 20%로 거의 투명하게 변경하고 안쪽의 텍스트를 감춘다
  3. 메세지의 이 “미니” 버전을 클릭했을 때, 화면의 중앙으로 되돌아온다 (즉, 우리가 축소시키도록 했던 작업과 반대 작업)

그리고 사용자가 그들의 메세지에서 무슨 일이 일어나는지 알 수 있도록 풀사이즈 메시지에서 미니 메시지로 전환은 애니메이션되어야 한다 (사용자들은 메시지가 화면의 구석으로 “축소”되는 것이 볼 수 있다).

jQuery로 애니메이션을 수행하는 것은 간단하다: 단지 .animate() 함수를 사용하고 애니메이션의 마지막 결과가 무엇인지를 (그리고 걸리는 시간이 얼마나될지를) 제공하면 된다:

$(ourObject).animate({
    width: "20px", height: "20px", top: "20px",
    right: "20px", marginRight: "0px", opacity: "0.2"
  }, 300);

이것은 ourObject 라는 것을 얻어와서 300 밀리초 동안 너비와 높이는 20px, 상단과 우측 20px 지점으로 margin-right 스타일 속성은 0px, (불투명도를 지원하는 브라우저에서) 불투명도는 20%로 변경시킬 것이다. 이 애니메이션이 메시지가 클릭되었을 때 발생하도록하기 위해 jQuery 스타일로 프로그래밍하는 문제만 남았다:

$(ourObject.click, function(){
  $(this).animate({
    width: "20px", height: "20px", top: "20px",
    right: "20px", marginRight: "0px", opacity: "0.2"
  }, 300)
});

다시 한번 클릭되었을 때 메시지를 복원하는 것은 또 다른 .animate() 를 호출하면 된다:

$(ourObject).animate({
    width: "400px", height: "75px", top: "50px",
    right: "50%", marginRight: "-200px", opacity: "0.9"
  }, 300);

그리고 메시지가 현재 보이는 상태인지 축소된 상태인지를 알려주기 위한 (그래서 어떤 애니메이션을 동작시킬 것인가를 알 수 있도록) 약간의 로직과 메시지의 초기 스타일(크고, 녹색이고, 수평적으로 중앙에 위치하도록 한다) 을 기술하기 위한 CSS, 이것이 필요한 전부이다. 스크립트 단 30 줄이다. 이것이 라이브러리들이 좋은 이유이다!

CSS 전이

마지막으로, (모두가 아닌) 일부 애니메이션들은 사실은 자바스크립트가 전혀 없이 만들어질 수 있다! Safari 나 다른 Webkit 기반의 브라우저들, 그리고 Firefox 3.1 은 자바스크립트를 사용하지 않고 CSS 값을 다른 값으로 부드럽게 전이되게 할 수 있다. 이 코드는:

div { opacity: 1; -webkit-transition: opacity 1s linear; }
div:hover{ opacity: 0; }

div 위로 마우스가 오버될 때 지원가능한 브라우저에서는 1초동안 부드럽게 희미해지도록 만들어줄 것이다. 이 CSS 전이는 아주 새롭지만 가장 최신 브라우저를 제외한 어떤 브라우저에서도 지원되지 않는다. 그래서 만약 당신의 애니메이션이 대부분 사람들에게 동작하도록 하고 싶다면 DOM 스크립팅을 사용할 필요가 있다.

요약

자바스크립트를 이용한 웹페이지의 애니메이션 기능에 대해 정리하자면 다음과 같다 – setTimeoutsetInterval함수를 이용한 제1원리로 만들어진 몇가지 애니메이션 예제를을 경험해 보았으며 더 빠르게 애니메이션을 만들기 위해 자바스크립트 라이브러리들을 어떻게 사용하는지에 대해 알아보았다.

연습 문제

  1. setTimeoutsetInterval의 차이는?
  2. setInterval이 없다면, 어떻게 이 함수를 에뮬레이트할 것인가?
  3. 1.5초동안 20단계로 엘리먼트를 완전히 보이는 상태에서 완전히 안보이는 상태로 페이드하려면 어떻게 하면될까?
  4. 1.5초동안 20단계로 엘리먼트를 완전히 보이는 상태에서 완전히 안보이는 상태로 페이드했다가 다시 보이는 상태로 되돌릴려면 어떻게 하면될까?

저자에 관하여

Stuart Langridge는 아마도 세상에서 컴퓨터 사이언스와 철학 두 개의 학사학위를 갖고 있는 유일한 사람일 것이다. 그가 컴퓨터에 대해 시시하게 느끼지 않을 때는 GCap Media 에서 자바스크립트, 장고(Django), 파이썬 해커이며 SitePoint의 DHTML Utopia 저자이며 상당한 주량의 술꾼이다. 그는 세계 최초의 자유 오픈소스 소프트웨어 라디오 쇼인 LugRadio 에서 팀의 1/4을 맡고 있다. 그의 웹, 스크립팅, 오픈 소스 소프트웨어에 대한 방랑들과 과거에 떠돌던 무엇이든 간에 kryogenix.org 에서 찾을 수 있다; Stuart 는 밖에 나가 흡연구역을 찾아보면 찾을 수 있다.

  1. flickbook: 일련의 그림들로 이루어진 책으로 엄지손가락으로 튕기면 마치 애니메이션을 보는 듯한 효과를 주는 만화책의 일종
  2. Wile E. Coyote : 루니툰 애니메이션 시리즈에 나오는 코요테 캐릭터. 시리즈명은 “Adventures of the Road-Runner”로 코요테가 로드러너(뻐꾸기새의 일종)를 잡으려고 하지만 항상 로드러너에게 당하는 내용의 만화. 로드러너의 “밍밍”소리가 귓가에 선하다 -_-;;
이 글을 다 읽어주셨다면, 댓글을 남겨주세요. 좋았다라는 격려도 좋고, 잘못된 부분을 지적해 주시는 것도 좋습니다. 마음에 드셨다면 아래 Like 버튼을 눌러서 페이스북과 트위터로 소개해 주시면 더욱 좋겠습니다.