requestAnimationFrame とタイマーの今更な比較とデモ

フレーム描画のタイミング制御モデル

今更 requestAnimationFrame() だなんて Can I use を見ても IE 10 ですら実装していて、世界の 9 割以上で動作しうるカバレッジなわけですが改めて setTimeout() との違いを表現しうるサンプルをこさえてました。

それぞれの仕様の詳細や挙動についてはそれこそ今更なのでググっていただくとして、この記事ではサンプルについて曖昧な説明を添えていきます。

CodePen - setTimeout vs requestAnimationFrame (with CSS Transitions)

See the Pen setTimeout vs requestAnimationFrame (with CSS Transitions) by Ayumu Sato (@ahomu) on CodePen.

比較のポイントとシナリオ

  • setTimeout()requestAnimationFrame() の比較
  • 継続的な Jank の適用( enable continuous jank )
  • CSS Transitions との比較( enable css samples )
  • will-change の適用( enable will-change )

シナリオとしては「継続的に細かい Jank をぶつけたときの FPS 低下量の比較」や「CSS Transitions とスクリプトアニメーションの比較」が主です。will-change はオマケですが、Composite Layer 向けラスタライズの発生タイミング比較とかを観察できます。

Web Animations について、動く原理は CSS Transitions/Animations と大差ないやろということでオミットしています。

細かい Jank をぶつけると setTimeout はしぬ

continuous jank を有効にすると、setInterval(jank, 4) として次の関数が実行されるようになります。メインスレッドを 8〜20 ms 程度ブロックする処理が高頻度で実行されるようになります。激しいですね。

function jank() {
  performance.mark('jank-s');
  const s = performance.now();
  const e = rand(8, 20); // busy time (ms)
  do {
    if (performance.now() - s > e) {
      break;
    }
  } while (true);
  performance.mark('jank-e');
  performance.measure('jank', 'jank-s', 'jank-e');
}

このような処理が頻発すると、setTimeout ベースのアニメーションはフレーム処理のチェーンがドンドン遅れていくので FPS が低下していきます。

Timeline で確認すると次のような感じです。 jank()draw() に User Timing API を仕込んでいるので、User Timing のラインに jank と draw が表示されるのが分かります。...が、draw は実行時間が短くて見えないので次のスクショにはマーキングで示しています。


setTimeout 時の draw 関数がコールされるタイミング


setTimeout() は Jank におされて、Draw のタイミングがマチマチになっていたり遅れていたりするように見えるのが分かります。メインスレッドに紫色のイベントが見えるあたりが描画タイミングですが、特に同期もできていません。

次のスクショは比較対象として requestAnimationFrame() を利用したときのタイムラインです。Jank に汚染はされているのでロングフレーム(16msを超えて赤く示されるフレーム)は発生しますが、描画可能なタイミングがあれば Animation Frame として都度呼び出されるので比較的コンスタントにコールされています。


requestAnimationFrame 時の draw 関数がコールされるタイミング


ちょっと極端な箇所で比較用のスクリーンショットをとってしまった感はありますが、実際にサンプルを試すと FPS やアニメーションの見た目上の違いが分かるはずです。たぶん、きっと。端末スペックとかブラウザによっても現れやすさの違いがあるのですが、Chrome だと比較的極端な結果が出やすいようです。

大きい Jank をぶつけると requestAnimationFrame もしぬ

当たり前ですが、デカい Jank があると requestAnimationFrame() と言えども Animation Frame のチャンスを掴めずにその実効 FPS をガンガン減らしていきます。どんなスクリプトアニメーションでも 60 FPS を保証してくれるような魔法の杖ではないことを改めて覚えておくべきでしょう。

以上です

今更なので参考も後記もないです。敢えて付け加えるならばこれで比較実証のサンプルとして正しく成立しているのかの心配がある...( ˘ω˘)