実践JavaScript アニメーション#1

「プログラミング経験はそこそこだけど、JavaScriptはあんまり」な人向け社内勉強会資料vol.5

Shogo Ohta, 2009-08-24

JavaScriptによるアニメーション

アニメーションの基本

JavaScriptでアニメーションを実現する方法を紹介します。

まずは失敗ケースです。

var box = document.getElementById('box');
for (var i = 0; i < 100; i++){
  box
.style.left = i + '%';
}

このコードはboxという要素のleftの値を1%ずつ増加させています。一見するとこれで左から右にアニメーションできそうに思えますが、実際には最後の100%の位置しか表示されません。これはブラウザがスタイルの変更を反映(再描画)するタイミングがスクリプトの実行後にしかないためです。

つまり、アニメーションするためには、再描画を行わせる必要があります。これにはsetTimeout、setIntervalというタイマー関数を使用して処理を分断することで実現します。

var box = document.getElementById('box');
var i = 0;
var timer = setInterval(function(){
  box
.style.left = i++ + '%';
 
if (i > 100) {
    clearInterval
(timer);
 
}
},10);

これでアニメーションを実現できました。しかし、この方法では必ず100回再描画が行われるため、仮に1回の再描画に1秒かかる環境では100秒、10msで再描画できる場合、1秒でアニメーションすることになってしまいます。無駄に長いアニメーションはユーザーにストレスを与えるので、このままでは使用できません。

そこで、何回再描画するか、という指定方法から、何秒間アニメーションするか、という指定方法に切り替えます。ハイスペックな環境ではそれに応じて多くの再描画が行われ、ロースペックな環境では少ない再描画で処理を済ませ、全体の実行時間を同じにするという方法です。

var box = document.getElementById('box');
var begin = new Date()*1;/* Dateオブジェクトは数値に型変換するとシリアル値を返す */
var timer = setInterval(function(){
 
var now = new Date()*1;
 
var progress = (now-begin) / 10;/* (now-begin) / 1000 * 100 */
  box
.style.left = progress + '%';
 
if (progress > 100) {
    clearInterval
(timer);
    box
.style.left = '100%';
 
}
},10);

Easing関数

上述のサンプルは単純な等速直線運動でしたが、簡単にバリエーションを追加できます。

「経過時間、初期値、変動値、継続時間」の4つから、現在値を求めるEasing関数が広く使われています。

var box = document.getElementById('box');
var begin = new Date()*1;
var from = 0;
var to = 100;
var distance = to - from;
var duration = 1000;
var timer = setInterval(function(){
 
var now = new Date()*1;
 
var time = now-begin;
 
var progress = distance * time / duration + from;
  box
.style.left = progress + '%';
 
if (time > duration) {
    clearInterval
(timer);
    box
.style.left = to + '%';
 
}
},10);

Easing関数は自作することもできますが、Tweenerに搭載されている関数はhttp://hosted.zeh.com.br/tweener/docs/en-us/misc/transitions.htmlにまとめられています。

JavaScriptでは、JSTweeenerにこれらの関数群が移植されているので、これを利用できます。

var easing = JSTweener.easingFunctions.easeOutBounce;
var box = document.getElementById('box');
var begin = new Date()*1;
var from = 0;
var to = 100;
var distance = to - from;
var duration = 1000;
var timer = setInterval(function(){
 
var now = new Date()*1;
 
var time = now-begin;
 
var progress = easing(time, from, distance, duration);
  box
.style.left = progress + '%';
 
if (time > duration) {
    clearInterval
(timer);
    box
.style.left = to + '%';
 
}
},10);
参考:
http://piro.sakura.ne.jp/latest/blosxom/mozilla/xul/2009-04-08_tween.htm
http://subtech.g.hatena.ne.jp/secondlife/20090408/1239172269