【マイレース開発記録(16)】requestAnimationFrameの理解
フロントエンドの実装に入ってから、私は思った以上に「アニメーション」という壁にぶつかりました。
バックエンドのロジックは比較的整理して進められていたのですが、ブラウザ上で「うさぎが走るレース」を表現する段階で、思うように動かない状況が続いたからです。
特に大きな転機になったのが requestAnimationFrame の理解でした。
canvasで「レース」を動かすという課題
マイレースでは、結果を単なる数値で表示するのではなく、5匹のうさぎがレーンを走る レース形式の可視化 を採用しています。
そのため、JavaScript側では
- うさぎの位置をフレームごとに更新する
- 画面を何度も描き直す
- ゴールした順番を判定する
といった処理が必要になります。
最初は、単純に「一定時間ごとに処理を呼び出せばいい」と考え、JavaScriptの setInterval を使ってアニメーションを作ろうとしました。
しかし、すぐに問題が出てきました。
- 動きがカクカクする
- ブラウザの負荷が高くなる
- 描画タイミングが不安定になる
レースとして見せるには、どうしても「自然な動き」が必要でした。
requestAnimationFrameという仕組み
そこで調べていく中で出会ったのが、requestAnimationFrame というブラウザAPIです。
これは、
- ブラウザの描画タイミングに合わす
- 次のフレームの処理を呼び出す
ための仕組みです。
マイレースでは、次のようなループでアニメーションを回しています。
JavaScript
function lp(){
if(!st.r) return;up(); // 状態更新
dr(); // 描画requestAnimationFrame(lp);
}
この処理は、
- レースの状態を更新する
- canvasに描画する
- 次のフレームで同じ処理を呼び出す
という流れを繰り返します。
レースの状態を更新する仕組み
レースの進行は、フレーム数を使って管理しています。
JavaScript
function up(){
st.f++;rs.forEach(r=>{
if(!r.e){
let p = Math.min(st.f / r.ff, 1);
r.x = SX + (GX - SX) * p;if(p >= 1) r.e = 1;
}
});
}
- フレーム数を増やす
- 進行率を計算する
- うさぎの位置を更新する
という処理です。
このように フレームを基準に位置を計算することで、レース全体の動きが安定する ようになりました。
canvasへの描画処理
状態更新とは別に、描画処理も毎フレーム実行します。
JavaScript
function dr(){
cx.clearRect(0,0,cv.width,cv.height);rs.forEach(r=>{
drawRabbit(r.x, r.y0);
});
}
ここでは、
- 背景を描く
- レーンを描く
- うさぎを描く
といった処理を順番に行っています。
canvasは「毎回描き直す」方式なので、状態更新と描画を分ける設計 が重要でした。
最初は理解できなかった部分
正直に言うと、requestAnimationFrameの仕組みは、最初はほとんど理解できていませんでした。
「なぜこの関数を自分自身で呼び続けるのか」
「なぜsetIntervalではダメなのか」
という部分が腑に落ちず、何度もコードを書き直すことになりました。
ここでもAIとの対話が役立ちました。
- 処理の流れを言語化する
- 小さなサンプルを書いて検証する
- 実際にブラウザで動きを確認する
こうした作業を繰り返すことで、「ブラウザが描画するタイミングに合わせる」というrequestAnimationFrameの考え方が少しずつ理解できてきました。
所感
今回のフロントエンド実装では、「動いている画面」を作る難しさを強く感じました。
バックエンドでは、ロジックが正しければ結果は安定して出ます。
しかし、フロントエンドでは
- 見た目
- タイミング
- 体感的な滑らかさ
といった要素がすべて関わってきます。
そのため、単に動くコードを書くのではなく、ユーザーがどう感じるかを考える必要がありました。
次回予告
うさぎが走るだけでは、マイレースの特徴はまだ表現できません。
次の課題は、「ジャンプ」という要素をどう表現するか でした。
しかし、このジャンプ処理が思った以上に難しく、レースの挙動が完全に崩れてしまうことになります。
次の記事は、
として、ジャンプアニメーションの実装で起きた問題と、その修正について書いていきます。


