애플 홈의 아이폰 4S 제폼 웹페이지에서 메인 비주얼를 장식하는 슬라이드는 총 6개의 애니메이션 시퀀스로 구분된 제품 특징들로 구성되어 있습니다. 이 제품 슬라이드를 구현하는 CSS 애니메이션과 자바스크립트 코드를 사례로 효과적인 애니메이션을 구현하는 방법에 대하여 공부해 봅시다.

아래의 데모는 작동원리를 이해하기 위해 약 20% 수준으로 축소한 것이며, 브라우저의 뷰포트를 확인하기 위한 가상 요소(browser)가 포함되어 있습니다. 슬라이드 요소들이 커다란 하나의 맵(phone-stage)으로 구성되어 있고 아이폰 검정색(bphone)만 별도의 레이어로 취급되고 있습니다. 그리고 슬라이드 6번은 1번 슬라이드와 절묘하게 교차하면서 반복된다는 사실을 알 수 있습니다. 그러나 실제 버전에는 각 슬라이드에 사용된 이미지와 텍스트가 개별 요소들로 구분되어 보다 많은 구성 요소들로 이루어져 있으며, 링크와 텍스트 애니메이션 정보 등의 처리까지 포함하고 있지만 이 데모에서는 그 것을 통합하거나 생략한 것입니다.

데모 출처: http://johnbhall.com/iphone-4s/ via John B. Hall

슬라이드 기능을 만드는 자바스크립트 핵심 함수는 SlideSequencer 클래스입니다. Sequencer는 재생/정지 상태를 관장하고 정해진 시간에 다음 시퀀스를 재생합니다. Slide는 애니메이션 대상 요소와 이벤트 핸들러, CSS transformtransition 속성에 사용될 translate, rotate 등의 값들로 구성된 인스턴스를 생산합니다. 대상이 된 HTML 요소에 스타일(style) 속성이 인라인으로 갱신되면서 애니메이션이 발생하는 것입니다. 파이어버그 또는 돔인스펙터(개발자 도구)를 열고 "phone-stage"의 스타일 속성을 감시해 보세요. 위 대모에 사용된 코드는 다음과 같습니다.

  var phoneStage = $("phone-stage"),
      bphone = $("bphone");
  
  var q = new Slide([{
      el: phoneStage,
      x: -325,
      y: -234,
      r: -30
    }, {
      el: bphone,
      x: 0,
      y: 0,
      r: -30
    }]),
  
  t = new Slide([{
      el: phoneStage,
      x: -264,
      y: -316,
      r: -0
    }, {
      el: bphone,
      x: -300,
      y: -300,
      r: -30
    }]),
  
  ...
  
  b = new Sequencer([q, t, z, u, j, f, v]);
  b.play();

즉, 이 슬라이드에서 발생하는 애니메이션은 전부 CSS로 처리하지만 이것을 제어하는 것은 자바스크립트입니다. 자바스크립트는 시퀀스를 구성하는 transition과 transform 속성을 만들어 해당 요소의 인라인 스타일 속성에 대입해 주는 거죠. 이 간단한 원리로 얻어지는 가치는 무엇일까요? 자, 우리는 일반적으로 애니메이션을 처리할 때 jQuery에서 제공하는 .animate 또는 Prototype & Scriptaculous의 .morph와 같은 자바스크립트 기반 애니메이션 함수를 사용해 왔습니다. 왜냐하면 사용자의 미세한 행동들 까지도 감지하여 화면에 응답하는 것을 만들어야 했기 때문입니다. 그리고 이 애니메이션 함수들은 Frame-by-Frame으로 연산하기 때문에 생각보다 많은 처리 비용이 든다는 사실을 여러분은 잘 알고 있을 것입니다.

CSS로 처리되는 애니메이션을 자바스크립트로 제어한다는 것은 곧 화면상의 상호작용을 직접 제어할 수 있게 되는 것을 의미합니다. 이것은 매우 흥미롭습니다. .animate.morph를 사용하는 대신에 CSS 속성을 활용하여 브라우저의 자원을 효율적으로 분산 처리하는 효과적인 애니메이션 구현 기법입니다. 이렇게 확보된 자원으로 더욱 복잡한 애플리케이션을 구상할 수 있고, 아름다운 그래픽 애니메이션도 지원 받을 수 있습니다. 특히 CSS transitions는 하드웨어 가속을 지원하기 때문에 자바스크립트 연산으로는 도저히 답이 안나오는 여러가지 사치성(호화로운) 애니메이션 효과들을 제품에 적용여 네이티브 애플리케이션에 하이킥을 날리는 레알 웹애플리케이션을 만들어 낼 수 있습니다.

Comments

요즘 OpenStack Object Storage(Swift)를 가지고 놀고있습니다. Swift는 아마존(Amazon)의 S3 서비스와 유사한 오픈소스화 된 오브젝트 스토리지 서비스이며 최근 주목받는 클라우드 솔루션입니다. 두드러진 특징은 ReSTful API를 제공한다는 것입니다. 그래서 웹서버를 통하지 않고 클라이언트(예를 들면 웹브라우저 또는 모바일 애플리케이션)에서 직접 접근하여 관리할 수 있는 인터페이스를 제공합니다. Authentication(인증)과 Container/Object로 이루어진 간단한 계층 구조를 가지며 동일한 URI에 GET, POST, PUT, DELETE 등의 HTTP 메서드를 이용하여 행동을 구분하고, 요청 헤더(Request Header)를 조작하여 인증을 처리합니다. Swift에 대한 소개는 이즘에서 마치고 본론으로 들어가겠습니다.

일단 Node.JS를 이용하여 실험용으로 Swift기반 클라우드 데이터 스토어 클라이언트를 개발하고 있습니다. 클라이언트에서 직접 접근하는 방법과 웹서버를 통하는 두가지 방법 중 후자를 선행하기로 한 것입니다. 전자의 경우 인증 계정과 키를 클라이언트가 관리하게 되는데 웹이라는 특성상 보안 취약점이 발생할 수 있다는 위험성이 있으며, 만약, 클라이언트가 웹브라우저라면 헤더를 조작하는 수단이 천상 Ajax로 날리는 수 밖에 없는데 응답이 바이너리여서 뭔가 엄청난 시련이 다가올 것 같은 기운이 엄습했기 때문이었죠. 기가-바이트급 Base64 스트링을 자바스크립트로 처리한다고 생각해 보세요.(헐~) 후자는 파일을 송/수신하는 과정에 웹서버를 거치기 때문에 불필요한 I/O가 발생하여 성능이 떨어질 것을 감안하고 작업에 착수했습니만, 그 예상은 하루만에 뒤집혔습니다. Node.JS에서 파일 전송을 매우 효율적으로 처리할 수 있는 방법을 발견하여 이곳에 공유합니다.

다음 자바스크립트 코드는 서버-사이드에서 작동하는 코드로 HTTPS 프로토콜을 사용하는 Swift 서비스의 REST에 접근하도록 만들어진 공통 요청 함수입니다. 이 함수는 swift라 명명한 모듈에 포함되어 있습니다. 첫 번째 인자에서 HTTPS 요청에 필요한 옵션들을, 두 번째 인자는 HTTPS 요청에 대한 결과를 돌려주는 콜백을, 세 번째 인자는 생략 가능한 응답 객체입니다.

function request(options, callback, pipe) {
  options = extend({
      host: this.options.host
    , port: this.options.port
    , path: '/auth/v1.0'
    , method: 'GET'
    , headers: {
        'X-Auth-Token': this.token
      , 'X-Storage-Token': this.token
    }
  }, options);

  options.headers['User-Agent'] = 'Node.JS Swift API Client';
  options.path = encodeURI(options.path);

  var client = https.request(options, function(res) {
    var buffers = [];
    if (pipe) {
      pipe.header('Content-Length', res.headers['content-length']);
      pipe.header('Content-Type', res.headers['content-type']);
    }

    res.on('data', function(buffer) {
      if (pipe) pipe.write(buffer);
      else buffers.push(buffer);
    });

    res.on('end', function(err){
      buffers.length && clog.info(res.statusCode, res.headers);
      res.body = buffers.join('');
      callback && callback(null, res);
    });
  });

  client.on('error', function(err) {
    callback && callback(err);
    client.end(err);
  });

  client.end();
}

소스가 전문이 아니기 때문에 세 번째 인자로 받은 응답 객체에만 집중하겠습니다. 이 객체는 Node.JS에서 구축한 HTTP 웹서버의 응답 객체를 그대로 전달한 것입니다. 아래의 코드는 Express를 이용하여 구성한 라우팅 룰들 중에서 클라이언트의 파일 다운로드 요청을 처리하는 부분입니다.

var swift = new Swift({
    user: 'auth-user'
  , pass: 'auth-key'
  , host: 'auth.api.yourcloud.com'
  , port: 443
});

...

app.get('/store/:container/:object', function(req, res){
  var container = req.params.container
    , object = req.params.object;

  request.call(swift, {
    path: '/v1.0/' + swift.account + '/' + container + '/' + object
  }, function(result, headers) {

    ...

    res.end()
  }, res);
});

Express로 구성된 웹서버(80 포트)는 클라이언트가 요청한 정보와 인스턴스로 생성된 인증정보를 조합하여 Swift 서버(443 포트)로 다시 요청합니다. 이 처리 과정은 마치 두 부분으로 나뉜듯 보이지만 비동기로 짬뽕이 된 단 하나의 사이클이라는 사실을 기억하세요. 자, 이제 request 함수에서 받는 세 번째 인자의 흐름을 유심히 살펴 봅시다. 세 번째 인자가 있는 경우 Swift 서버로 요청한 바이너리 데이터를 수신한 만큼의 버퍼가 실시간으로 app.get의 응답객체(res)에 전달됩니다. 풀어서 말하면, 스토리지 서버에서 지금 막 내려온 청크(Chunk) 데이터를 받는 즉시 그냥 클라이언트 응답에 막 싸질러 버리는 거에요. 이 방법으로 바이너리 데이터를 가장 효율적이면서도 신속하게 클라이언트에 전달할 수 있었습니다. 기가-바이트급 데이터를 다운로드했는데 클릭과 동시에 속 시원하게 다운로드 게이지가 차더군요.

Node.JS에 또 한번 감동먹는 순간이었습니다. 졸라 간단하지 않나요? 파일 서버에서 받은 바이너리를 메모리에 캐시하는 동안 버퍼가 쌓이면 다시 스트림으로 얻어낸 청크 데이터를 클라이언트로 내려주는 파이프라인 로직을 어거지로 구현한 하루 분량의 소스 코드를 스스로 삭제하면서 세상 참 허무하다는 생각을 했습니다. 여기에는 Swift를 예로 들었지만 웹서버에서 다른 스토리지 서버에 위치한 바이너리 데이터를 클라이언트에 전달할 때에도 유용하게 사용될 수 있을 것입니다. 그리고 업로드 또한 이와 유사한 로직으로 구현할 계획인데 잘 될지 모르겠네요.

Comments

런던에서 열린 Future of Web Apps 컨퍼런스에서 Chris Coyier씨가 발표한 슬라이드입니다. Wufoo에서 CSS를 다루는 과정과 자주 범하는 실수들 그리고 가짜 요소(pseudo elements)들의 활용법에 대한 내용을 담고 있습니다. 아래 슬라이드는 Speaker Deck에 등록된 것입니다.

Comments