12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

requestAnimationFrameのループはPromiseに変換すると簡潔に書ける(かも)

Last updated at Posted at 2022-03-22

requestAnimationFrame()は、フレームレートを考慮してコールバック関数を実行する関数です。主にcanvasなどのアニメーションで使います。

このrequestAnimationFrameですが、コールバックに渡した関数は1度だけ実行されます。

globalThis.requestAnimationFrame(()=>{
  // この部分は1度だけ実行される
  console.log("raf!");
});

これをループさせて、複数回実行させるためには、例えばこのサイトでは以下のような方法が紹介されています。

const startTime = Date.now(); //描画開始時刻を取得
(function loop(){
  globalThis.requestAnimationFrame(loop);
  console.log(startTime - Date.now()); //経過時刻を取得
})();

名前付きの即時関数を使って実装されているのですが、ループしていることが一目では分かりづらくなっています。
ループを抜けるための条件分岐が足されると、更に読みづらくなってしまいそうです。

ループを簡潔に書くためにはどうしたらよいでしょうか。

requestAnimationFrameをPromiseに変換する

requestAnimationFrameをPromiseに変換することで、ループが書きやすくなります。

requestAnimationFrameをPromiseに変換
function animationFramePromise() {
  return new Promise<number>((resolve) => {
    globalThis.requestAnimationFrame(resolve);
  });
}

Promiseに変換した後は、ループを以下のように書くことができます。

const startTime = Date.now(); //描画開始時刻を取得
while (true) {
  await animationFramePromise(); // この行で、次の更新タイミングまで待つ
  console.log(startTime - Date.now()); //経過時刻を取得
}

while文を使うことで、ループであることが一目で分かるようになりました。

ループを抜ける条件を書く時は、while文のbreak条件の部分に指定するか、if文でbreakするだけです。

const startTime = Date.now();
while (startTime - Date.now() < 10000) { // 終了条件
  await animationFramePromise();
  console.log(startTime - Date.now());

  // 終了条件はこう書くこともできる
  // if (startTime - Date.now() > 10000) {
  //   break;
  // }
}

setTimeoutにも応用できる

ちなみにこの方法、setTimeoutやqueueMicrotaskにも応用できます。

function delay(ms) {
  return new Promise<number>((resolve) => {
    globalThis.setTimeout(resolve, ms);
  });
}

const startTime = Date.now(); //描画開始時刻を取得
while (true) {
  await delay(1000);
  console.log(startTime - Date.now()); //経過時刻を取得
}

setTimeoutをPromise化する際の注意点として、時間計測のスタート地点が「前の処理が終了した時刻」になります。そのため、例えば1000ミリ秒を指定してもぴったり1000ミリ秒間隔になるわけではありません。

GUIの操作など、「ぴったりn秒」が要求されない箇所では、setIntervalよりこちらのほうが書きやすい事もあるかもしれません。

まとめ

  • requestAnimationFrameを使ったループ処理はPromiseに変換すると簡潔に書ける
  • setTimeoutやqueueMicrotaskも同様にPromiseに変換すると簡潔に書ける
12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?