-
Notifications
You must be signed in to change notification settings - Fork 0
Home
WMClock.js は、タイムラインアニメーションに最適なクロックと、描画テストに必須となる仕組みを提供します。
タイムラインアニメーションは、ゼロから始まる時間軸と、軸上にマップされたイベントから構成されています。
0ms 200ms 400ms 600ms 800ms 1000ms 1200ms 1400ms 1600ms 1800ms
+------+------+------+------+------+------+------+------+------+
| | | | | | | | | |
+------+------+------+------+------+------+------+------+------+
^
スプライトA を表示する
^
スプライトB を表示する
^
スプライトA を移動させる
潤沢なCPU/GPU性能があれば、これらの全てのイベントを正確に再生することも可能ですが、 モバイルブラウザにおいては、それはまだまだ夢の様な話になります。
また、レンダリングのテストを全て人の手で行うという考え方は、QA/テスト工学的にナンセンスです。
日々増え続ける iOS, Android デバイスに対応しきれず、いずれテスト工数が爆発し、テスト体制が崩壊してしまうでしょう。
低スペックなデバイスでも無駄なくアニメーションをドライブし、 かつ、可能な限り自動テストが可能なようにするためには、 ブラウザとJavaScriptが抱える以下の課題を全てクリアする必要があります。
以下は、よくあるアニメーションを再生するコードです。
var lastTimeStamp = Date.now();
function tick() {
setTimeout(tick, 1000 / 60);
var timeStamp = Date.now();
var deltaTime = timeStamp - lastTimeStamp;
render(timeStamp, deltaTime);
}
setTimeout(tick, 1000 / 60);
このコードは幾つかの課題を抱えています。
setTimeout(, 1000 / 60)
は16.666ms毎にコールバックが発生し、1秒間に60回画面を更新できそうに見えますが、実際の動作はかなり異なります。
試した事がある方は、このようなコードでは安定して60fpsを出せない事をご存知でしょう。
これを簡単に改善しようとして setTimeout(, 1000 / 70)
としても、こんどは一秒間に60回以上画面を更新しようとしてしまいます。
ブラウザは独自のリフレッシュレートとタイミングで描画をします。過剰な再描画命令は実際には反映されず、無駄な負荷となります。
滑らかなアニメーションを実現するには、ブラウザに余計な負荷をかけずにできるだけfpsを上げる工夫が必要になります。
この課題を解決するための API が requestAnimationFrame と performance.now です。
requestAnimationFrame はブラウザのリフレッシュタイミングでコールバックを発生させるため、無駄な描画負荷を無くす事ができます。
以下が requestAnimationFrame を使い改善を行ったコードになります。
var lastTimeStamp = performance.now();
function tick(timeStamp) {
requestAnimationFrame(tick);
var deltaTime = timeStamp - lastTimeStamp;
render(timeStamp, deltaTime);
}
requestAnimationFrame(tick);
このコードでは Date.now() の省略も同時に行っており、さらなる負荷の軽減も同時に施しています。
setTimeout の代わりに requestAnimationFrame を使ったコードにも、アニメーションの描画テストができないという課題が残っています。
描画テストは安定したクロックの供給を必要としますが、 requestAnimationFrame は負荷に応じてコールバックタイミングが異なるため、現在時刻(timeStamp)と経過時間(deltaTime)が安定せず、 そのままではアニメーションのテスト結果が毎回異なってしまいます。
この課題を解決するのが以下のコードです。
timeStamp と deltaTime を 100ms 刻みの理想的な数値に整形しています。
var pulse = 100;
var lastTimeStamp = -1;
function tick() {
requestAnimationFrame(tick);
var timeStamp = 0;
var deltaTime = 0;
if (lastTimeStamp < 0) {
timeStamp = 0;
deltaTime = pulse;
} else {
timeStamp = pulse + lastTimeStamp;
deltaTime = pulse;
}
lastTimeStamp = timeStamp;
render(timeStamp, deltaTime);
}
requestAnimationFrame(tick);
また、60fpsで再描画される画面を目視で確認することは中々できません。 ゆっくりとアニメーションさせつつ描画を確認できるようにするには以下のようなコードも必要でしょう。
var speed = 1000;
var pulse = 100;
var lastTimeStamp = -1;
function tick() {
var timeStamp = 0;
var deltaTime = 0;
if (lastTimeStamp < 0) {
timeStamp = 0;
deltaTime = pulse;
} else {
timeStamp = pulse + lastTimeStamp;
deltaTime = pulse;
}
lastTimeStamp = timeStamp;
render(timeStamp, deltaTime);
}
setInterval(tick, speed);
タブが inactive (background) になった場合はクロックの供給をストップし、アニメーションを停止するのが望ましい場合もあるでしょう。
visibility change event をハンドリングは以下のようにします。
document.addEventListener("visibilitychange", handlePageVisibility);
function handlePageVisibility(event) {
var hidden = document.hidden;
callback(hidden);
}
上記のような理想的でシンプルなコードは visibility change event を実装している最新のブラウザでしか利用できません。
おそらくは https://github.com/uupaa/PageVisibilityEvent.js のような polyfill を含むライブラリを利用する必要があるでしょう。
WMClock.js はこれら全ての課題を解決する機能を提供します。
- 描画の再現テストに必要な、ある地点を基準とした相対時間の設定と取得
- options.baseTime
- WMClock#setTime
- WMClock#getTime
- WMClock#now
- ペースメイクされた理想的な経過時間の配給
- options.pulse
- 低クロックの配給
- options.speed
- ブラウザの描画サイクルと合致した低コストなクロックの配給
- options.vsync
- visibility change event への対応
- options.suspend