これでできる! クロスブラウザJavaScript入門

第18回アニメーションの実用

こんにちは、太田です。前回はアニメーションの基礎を学びました。今回はその基礎を実際に役立てるところを学んでいきます。

アニメーションを用いる場面と効果

さて、前回学んだようなアニメーションは、すなわち「状態の変化を見せる」ものでした。ある状態から次の状態へと時間をかけて変化させる処理を実装したものです。

なぜこういったアニメーションを行うのか、それには主に2つの理由・効果があります。

まず1つは読み込みの待ち時間をごまかす効果があります。サムネイル画像をクリックしたら、大きな画像の読み込みを開始しつつユーザーにはアニメーションを見せておき、読み込みが完了したら画像を表示するといったテクニックは、ユーザーの体感的な早さを改善する効果があります。

また、状態の変化が段階的に起こることで、その変化がどの程度の変化なのか直感的にわかる効果があります。例えば、あるリンクをクリックしたときに同じページ内のある位置にスクロールする場合、一瞬でスクロールするとスクロール前と後の違いがわからなくなることがあります。スクロールをアニメーションにすることで、どこからどこまでスクロールして、その間にどの程度のコンテンツがあったのかなど、ユーザーが得られる情報量を増やすことができます。

次のリンクは画面を下方向に500px移動させるスクリプトを実行します。1つ目は直接移動し、2つ目は0.5秒かけて移動します。

前者は移動前と移動後の繋ぎを見失ってしまいがちですが、後者は移動前と移動後が繋がっていて、ユーザーが状態を自然に把握することができます。

簡単なスムーズスクロール
function animateScrollBy(x, y){
  var begin = new Date() - 0;
  var prev = 0;
  var id = setInterval(function(){
    var current = new Date() - begin;
    if (current > 500){
      clearInterval(id);
      current = 500;
    }
    var diff = current - prev;
    prev = current;
    window.scrollBy(diff / 500 * x, diff / 500 * y);
  }, 10);
}

もちろん、アニメーション自体をコンテンツとするケースもないわけではありませんが、JavaScriptではちょっとしたユーザービリティの改善としてアニメーションを用いることがほとんどでしょう。

アニメーションのサンプル

ではもうひとつ簡単なアニメーションのサンプルを見てみましょう。今回もTwitterの検索APIを使用して、その結果を限られた範囲に表示するケース(ブログパーツのようなケース)を想定してみます。

発言1つ分の枠だけを用意して、マウスのホイールで前後の発言に移動する形にします(本来ならホイール以外にも移動ボタンを設置するべきですが、今回はサンプルなので省略します⁠⁠。

アニメーションは前回書いたMiniTweenerを使用します。

アニメーションサンプル
(function (){
  var button = document.getElementById('cbjs-18-3');
  button.onclick = function(){
    // 検索API呼び出し
    var script = document.createElement('script');
    script.src = 'http://search.twitter.com/search.json'+
      '?callback=TwitterCallback&lang=ja&q=http';
    document.body.appendChild(script);
  };
})();
function TwitterCallback(data){
  var root = document.getElementById('cbjs-18-2');
  var results = data.results;
  var ul = document.createElement('ul');
  ul.className = 'twl';
  var current = 0;
  var list = [];
  var scroller = function(evt, up){
    var prev = current;
    current += up || 1;
    if (current === len){
      current = 0;
    } else if (current === -1){
      current = len - 1;
    }
    // 前回のアニメーションはキャンセルする
    MiniTweener.end();
    MiniTweener(ul.style, {
      top:{
        from:prev * -80,
        to:current * -80,
        suffix:'px'
      }
    }, {
      duration: 500,
      easing: JSTweener.easingFunctions.easeOutCubic
    });
  };
  for (var i = 0, len = results.length;i < len; i++){
    var usr = results[i];
    var src = usr.profile_image_url;
    var li = document.createElement('li');
    var img = document.createElement('img');
    img.src = src;
    img.width = 48;
    img.height = 48;
    li.appendChild(img);
    var span = document.createElement('span');
    span.appendChild(document.createTextNode(usr.text));
    li.appendChild(span);
    ul.appendChild(li);
    list[i] = li;
  }
  root.appendChild(ul);
  var Down = -1, Up = 1;
  if ('onmousewheel' in document.body || window.opera){
    // onmousewheelが使えるか判定(Firefox以外はこちら)
    root.onmousewheel = mousewheel;
  } else {
    // 実質的にはFirefox用の処理
    root.addEventListener('DOMMouseScroll',
                          mousewheel, false);
  }
  function mousewheel(e){
    var ev = e||window.event;
    var dir = ev.wheelDelta || -ev.detail;
    dir = dir < 0 ? Down : Up;
    if (dir === Down){
      scroller(ev, 1);
    } else if (dir === Up) {
     scroller(ev, -1);
    }
    // preventDefault でスクロールをキャンセル
    if(ev.preventDefault) {
      ev.preventDefault();
    } else {
      //IEはreturnValue
      ev.returnValue = false;
    }
  }
}

アニメーションを使用する際のポイントは、なるべくアニメーションの影響範囲を小さくすることです。たくさんの要素をそれぞれ動かしたり、アニメーションで動かす対象の要素以外の要素に影響が出ないように注意する必要があります。

今回の場合、次のように各発言をli要素として静的に配置し、その親であるul要素を絶対位置指定にして位置を動かす方法を採用しました。

図1 HTMLの構造
図1 HTMLの構造

この方法は制御しやすいという長所がありますが、li要素が増えるほどアニメーションが重くなってしまう欠点もあるので注意が必要です。

場合によっては、前後の要素だけを動かすことでアニメーションに見せる方法がよいかもしれません。

まとめ

今回はアニメーションの実例を学びました。次回はCSSとDOMに関する部分(CSSOM)を中心に扱いたいと思います。

おすすめ記事

記事・ニュース一覧