to topto bottom

モバイルファースト時代のマルチデバイス対応を確実・簡単に

iOS6がサポートしたAnimation Timing APIのざっくり解説

こんにちは。x-fitチーム・エバンジェリストの渡辺です。iOS6からサポートされるようになったAnimation Timing APIですが、いったい何?という方もいらっしゃるかと思います(私自身がそう思いました)ので、ざっくりとまとめてみました。
 
 

Webサイトでアニメーションする方法と問題点

Webサイトでアニメーションするには、おもに3つの方法があります。一つ目は<animate>タグを使ってSVGで記述する方法、2つ目はCSS3で追加されたtransition系・animation系プロパティを使ってアニメーションさせる方法。もう一つが、主流となっているJavascriptを使って描く方法になります。
Javascriptで書く場合は、setTimeoutメソッド・setIntervalメソッドを駆使して、コールバックメソッドを呼び出して描画していくことになります。ここで制作者を悩ませるのが、この描画タイミングの更新周期をどのようにするかということです。サイトを見る端末がどのようなスペックか判断できない以上、経験と勘、テストでの結果で決めている、ということがほとんどだと思いますが、タイミングを細かくすればなめらかな描画になる一方、パフォーマンスに影響するリスクが増えてしまいます。

Animation Timing APIとは

Animation Timing APIは、W3CのWebPerformanceワーキンググループが策定したアニメーションに関する仕様で、現在はワーキングドラフト版となっています。このAPIは、すべてのブラウザで実装されているわけではありません。
このAPIが目的にしているのは、アニメーションの描画更新タイミング制御を、スクリプトからブラウザ側にリクエストできるようにすることです。ブラウザ側が描画タイミングを決定できるようにすることで、ブラウザ全体のアニメーション描画を最適化し、ブラウザが最小化された時などに、無駄にCPUパワーを消費しないよう抑制したりなど、最適なアニメーション描画を実現することができるようになります。

使い方

使い方のキーになるのは、requestAnimationFrameというメソッドです。2012/10月時点でははベンダープレフックスをつけて使用するため、以下のようになります。
  • Mozilla Firefox : window.mozRequestAnimationFrame
  • Google Chrome , iOS Safari and other WebKit based browsers : window.webkitRequestAnimationFrame
  • Microsoft Internet Explorer : window.msRequestAnimationFrame
  • Opera : window.oRequestAnimationFrame
取りうる引数は一つ、描画を行うコールバックメソッドになります。requestAnimationFrameを1回コールするたびに、描画メソッドがアニメーション処理の待ち行列に追加されるようなイメージになります。あとは、ブラウザが任意のタイミングで待ち行列から描画メソッドを取り出し、コールしていきます。連続的にアニメーションさせるためには、行列が空にならないよう描画メソッドを追加していく必要がありますので、コールバックメソッドの最後の部分で、requestAnimationFrameに自身のメソッドを追加しています。
アニメーションのキャンセルは、requestAnimationFrameメソッドコール時に返されるリクエストIDを引数として、cancelAnimationFrameをコールします。こうすることで、コールバックメソッドにキャンセルがコールされたことがマークされ、ブラウザがそのマークを見つけてアニメーションを終了します。

コールバックメソッドでの注意点

コールバックメソッドには描画処理を記述することになりますが、注意すべき点は、コールされる間隔が不定になったことを前提にする必要があるということです。「○秒かけて△pxを一定速度で移動」というようなアニメーションの場合、コールバックメソッドが実行されるたびに、時間を経過を判断して、どれだけ移動させるかを決定する必要があります。

サンプル

サンプルを作成してみました。300px四方のcanvasを置き、左上から右下に向かって5秒間で円をアニメーションさせます。サンプルでは、1秒間に縦横それぞれ60pxずつ移動させるように、コールバックメソッドが実行された段階で、前回実行時から何ミリ秒過ぎているかを取得して、移動先のピクセルを計算させています。
ブラウザの状態をいろいろと変えて(タブの枚数を増やして、同時に様々なサイトの処理をさせてみるなど)アニメーションを確認してみてください。ブラウザの負荷が高い時は、描画の頻度が減りますが、5秒間で移動が完了するように1回あたりの移動距離が大きく描画されると思います。
 
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
  content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Animation Timing API Sample</title>
<style type="text/css">
body {
  margin: 0px;
}

canvas {
  border: solid black 1px;
}
</style>
</head>
<body onload="init()">
<canvas id="canvas">
</canvas>
<input id = 'button1' type='button' value = 'Start Animation' onclick = startAnimation() /> 
<input id = 'button2' type='button' value = 'Stop Animation' onclick = stopAnimation() />
<div id = 'div1'>0
</div>
<script type="text/javascript">
  var canvas;
  var ctx;
  var w = 0;
  var h = 0;

  var px = 0;
  var py = 0;
  
  var animationStartTime = 0;
  var drawCount = 0;
  var previousTime = 0;
  var myReqId;

  var div1 = document.getElementById('div1') ;
  var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ;
  var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame || window.oCancelAnimationFrame ;

  function init() {
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
    //canvasの見た目の大きさと描画可能サイズを合わせます。
    canvas.style.width = '300px';
    canvas.style.height = '300px';
    canvas.width = canvas.offsetWidth;
    canvas.height = canvas.offsetHeight;
    w = canvas.width;
    h = canvas.offsetHeight;
  };

  function draw() {
  
    var time = Date.now();
    console.log("time = " + time);
    if(animationStartTime == 0){
      animationStartTime = time ;
      previousTime = time;
    };
    var curentSec =Math.floor(time/1000) ;
    var animationStartSec =Math.floor(animationStartTime/1000) ;
    
    ctx.clearRect(0, 0, w, h);
    
    //移動先のpxは、呼ばれた時間によって計算する
    px = px + 60 * ((time - previousTime) / 1000);
    py = py + 60 * ((time - previousTime) / 1000);
    
    ctx.beginPath();
    ctx.arc(px, py, 40, 0, 2 * Math.PI, true);

    ctx.fillStyle = "rgba(100, 100, 100, 0.5)";
    ctx.fill();
    drawCount++;
    div1.innerHTML = 'Average Frames Per Second : ' + (drawCount/(curentSec - animationStartSec)) ;
    previousTime = time;
    
    //canvasの範囲内でアニメーションする。
    if(px < 300 && py < 300){
      myReqId = requestAnimationFrame(draw);
    };
  };

  function startAnimation() {
    animationStartTime = 0;
    drawCount = 0;
    draw();
  };

  function stopAnimation() {
    window.cancelAnimationFrame(myReqId);
  };

</script>
</body>
</html>

実行結果

こちら(jsdo.it)でご覧いただけます。

多少のとっつきにくさはあるかと思いますが、setIntervalとsetTimeoutを試行錯誤して調整する苦労から解放されるメリットは、大変魅力的なものですね。

x-fitは、スマートフォン専用Webサイト制作における、Javascriptの試行錯誤から制作者の皆さんを開放します。ぜひ製品情報デモをご覧ください。

 

 

タグ: 
このエントリーをはてなブックマークに追加