アニメーションを実装する際のレンダリング負荷の軽減や環境差をなるべく無くす試行錯誤をした備忘録を残しておきます。
今回は、requestAnimationFrame()のフレームレートを制限する方法です。
環境は以下です。
OS macOS Catalina v10.15.7
参考記事
- Ensuring Consistent Animation Speeds
- フロントエンジニアなら知っておきたいブラウザレンダリングの仕組み
- ブラウザレンダリングの仕組み – zenn
- Canvasだけじゃない! requestAnimationFrameを使った アニメーション表現
前提
requestAnimationFrame()を使って作成したアニメーションは、デバイスのリフレッシュレートによって速度が大きく変わります。
警告: アニメーションが 1 フレームでどれだけ進んだかを計算する場合、常に第 1 引数(または現在時刻を取得する他の方法)を使用するようにしてください、そうしないと、アニメーションはリフレッシュレートの高い画面では速く実行されます。これを行う方法については、以下の例を参照してください。
Window.requestAnimationFrame() – mdn
以下のGIFがとてもわかりやすいと思います。

結論
fpsを現時点でベターな60で設定しています。
const flamesMap = {
limit: 60,//制限値
previousTime: performance.now(),
currentTime: 0,
deltaTime: 0,
};
let { limit, previousTime, currentTime, deltaTime } = flamesMap;
const interval = Math.floor(1000 / limit);
const startAnimation = () => requestAnimationFrame(loopAnimation);
const loopAnimation = (timestamp) => {
//フレームレートを制限する
currentTime = timestamp;
deltaTime = currentTime - previousTime;
if (deltaTime <= interval) {
startAnimation();
return;
}
previousTime = currentTime - (deltaTime % interval);
//ここに実行したい処理を追加
startAnimation();
};
startAnimation();
また、使用する箇所によって適宜調整するためにclassにしても良さそうです。
class LimitFlames {
constructor(limit) {
this.interval = Math.floor(1000 / limit);
this.previousTime = performance.now();
}
isLimitFlames(timestamp) {
const deltaTime = timestamp - this.previousTime;
const isLimitOver = deltaTime <= this.interval;
!isLimitOver &&
(this.previousTime = timestamp - (deltaTime % this.interval));
return isLimitOver;
}
}
const limitFlames = new LimitFlames(60);
const startAnimation = () => requestAnimationFrame(loopAnimation);
const loopAnimation = (timestamp) => {
if (limitFlames.isLimitFlames(timestamp)) {
startAnimation();
return;
}
// 実行したい処理を記述
console.log("実行したい処理を記述");
startAnimation();
};
startAnimation();
高リフレッシュレートなデバイスも増えてきているのでequestAnimationFrame() を使用する場合は、リフレッシュレートに依存しない実装は必須ですね。
また、以下の記事で紹介されているようなアニメーションの進捗をフレーム処理の外側から管理する方法もあるようです。
Canvasだけじゃない! requestAnimationFrameを使った アニメーション表現
普通のCSS transitionやGSAPともうまく使い分けていきたいです。