1. ホーム
  2. Web制作
  3. html5

HTML5タイマーrequestAnimationFrameの使い方を深く理解する。

2022-02-01 03:41:10

プリアンブル

javascriptのアニメーションでは、常にタイマーが中心となっています。そして、アニメーションのループを書く上で重要なのは、どの程度の遅延が適切かを知ることです。一方では、ループの間隔は、さまざまなアニメーション効果がスムーズで流動的に見えるように十分に短くなければなりません。一方では、ループの間隔は、ブラウザが結果の変化をレンダリングする能力を確実に持つように十分に長くなければなりません。

多くのコンピュータのモニターは60Hzでリフレッシュされ、これは1秒間に60回の再描画にほぼ相当する。それ以上の頻度で再描画してもユーザーエクスペリエンスが向上しないため、ほとんどのブラウザでは再描画の頻度をモニターの再描画頻度以下に制限しています。したがって、最も滑らかなアニメーションのための最適なループ間隔は、1000ms/60で、約16.6msに相当します。

そして、setTimeoutとsetIntervalの問題は、どちらも不正確であることです。これらの固有の動作メカニズムでは、intervalパラメータは、実際には、実行を待つためにブラウザUIスレッドのキューにアニメーションコードを追加する時間を指定するだけであることが決められています。他のタスクがキューの先頭に追加されている場合、アニメーションコードは実行する前に前のタスクが終了するのを待たなければなりません。

requestAnimationFrame はシステム時間間隔を使用して、最高の描画効率を維持します。間隔が短すぎて過剰描画になりオーバーヘッドが増加したり、間隔が長すぎてアニメーションが遅延して滑らかでなくなったりしないように、さまざまなウェブアニメーション効果に均一なリフレッシュメカニズムを持たせて、システムリソースの節約、システムパフォーマンスの改善、視覚効果の向上を実現します。

特徴

  • requestAnimationFrameは、各フレームのすべてのDOM操作を1回の再描画または再フローにまとめ、再描画または再フローの間隔は、ブラウザのリフレッシュ頻度に密接に対応します。
  • hiddenまたはinvisible要素では、requestAnimationFrameは再描画またはリフローされないため、もちろんCPU、GPU、メモリの使用量は少なくなります。
  • requestAnimationFrameは、アニメーションに特化してブラウザが提供するAPIです。ブラウザは実行時にメソッド呼び出しを自動的に最適化し、ページがアクティブでない場合はアニメーションが自動的に一時停止されるため、CPU オーバーヘッドを効果的に節約することができます。

使用方法

requestAnimationFrame メソッドは、引数としてコールバックを受け取り、コールバック関数には、現在 requestAnimationFrame() でソートされているコールバック関数がいつ起動されたかを示す DOMHighResTimeStamp というパラメータが渡されます。戻り値は、コールバックリスト内の一意な識別子を表すリクエスト ID です。この値は、コールバック関数をキャンセルするために window.cancelAnimationFrame() に渡すことができます。

requestID = window.requestAnimationFrame(callback);

このapiを使えば、次の再レンダリングにコードを入れて、短時間に大量のリフローを誘発するのを避けることができます。

例えば、ページスクロールイベントのコールバック関数はこのapiの良い候補で、次の再レンダリングまでコールバックを遅延させます。つまり、コールバックが実行される前に、同じコールバック関数で requestAnimationFrame を複数回呼び出すと、同じフレームでコールバックが複数回実行されることになります。最も単純には、スロットリング関数を使用してこの問題を解決するか、requestAnimationFrame のキューに同じコールバック関数を1つだけ持つ方法を見つけることができます。

let scheduledAnimationFrame = false;
document.body.onscroll = () => {
    if (scheduledAnimationFrame) return;
    scheduledAnimationFrame = true;
    window.requestAnimationFrame(() => {
        scheduledAnimationFrame = false;
        // do something
    });
};


もちろん、最適なアプリケーションシナリオはやはりインフレームアニメーションで、パフォーマンスを大幅に最適化することができます。

インタビューの質問

何万個ものデータをレンダリングしても、インターフェースに引っかからない方法

この質問では、ページを詰まらせることなくデータをレンダリングする方法、つまり、何万ものアイテムを一度にレンダリングするのではなく、DOMの一部を一度にレンダリングし、requestAnimationFrameで16ミリ秒ごとに更新できるようにする方法について見ています。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <ul>controls</ul>
  <script>
    setTimeout(() => {
      // Insert 100,000 pieces of data
      const total = 100000
      // Insert 20 items at a time, reduce if you feel bad about performance
      const once = 20
      // How many times does it take to render the data
      const loopCount = total / once
      let countOfRender = 0
      let ul = document.querySelector("ul");
      function add() {
        // Optimize performance, insertion does not cause reflow
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          li.innerText = Math.floor(Math.random() * total);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add);
        }
      }
      loop();
    }, 0);
  </script>
</body>
</html>

互換性

一部の古いブラウザはこのapiに対応していないため、このメソッドをカスタマイズしてウィンドウ下にマウントすることで、同様に使用することができます。

(function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; window.cancelAnimationFrame =
          window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

以上、本記事の全内容をご紹介しましたが、皆様の学習のお役に立てれば幸いです。また、Script Houseをより一層ご支援いただければ幸いです。