requestAnimationFrame vs setTimeout: 使い分けのガイドライン

JavaScriptでスムーズなアニメーションを構築したり、タスクをスケジュールする際、requestAnimationFrame
とsetTimeout
のどちらを選択するかは、アプリケーションのパフォーマンスに大きな影響を与えます。両方のメソッドとも関数の実行をスケジュールしますが、根本的に異なる目的を持ち、異なるタイミングメカニズムで動作します。
重要なポイント
requestAnimationFrame
はブラウザのディスプレイリフレッシュレートと同期して、スムーズな視覚的更新を実現setTimeout
は非視覚的タスクやバックグラウンド処理のための汎用タイマー- 間違った方法を使用すると、パフォーマンスの問題、バッテリー消耗、ユーザーエクスペリエンスの低下を引き起こす可能性
requestAnimationFrame
は非アクティブなタブで自動的に一時停止するが、setTimeout
は動作し続ける
基本的な違いの理解
setTimeout: 汎用タイマー
setTimeout
は、指定されたミリ秒の遅延後に関数を実行します。これは、ブラウザのレンダリングサイクルとは独立して動作する、シンプルで予測可能なタイマーです。
// 1秒後に実行
setTimeout(() => {
console.log('One second has passed');
}, 1000);
// パラメータ付き
setTimeout((message) => {
console.log(message);
}, 2000, 'Hello after 2 seconds');
setTimeout
の主要な特徴は、タスクキューベースの実行です。遅延時間が経過すると、コールバックはJavaScriptイベントループのタスクキューに参加し、他のタスクと実行時間を競合します。
requestAnimationFrame: アニメーション専用
requestAnimationFrame
(rAF)は、ブラウザの再描画サイクルと同期し、通常は多くのディスプレイで毎秒60フレームで動作します。これは視覚的更新専用に設計されています。
function animate(timestamp) {
// タイムスタンプに基づいてアニメーションを更新
const element = document.getElementById('animated-element');
element.style.transform = `translateX(${timestamp / 10}px)`;
// アニメーションを継続
if (timestamp < 5000) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
パフォーマンスとタイミングの考慮事項
ブラウザレンダリングパイプラインとの統合
requestAnimationFrame
の根本的な利点は、ブラウザのレンダリングパイプラインとの統合にあります。setTimeout
は遅延時間が経過するといつでも実行されますが、requestAnimationFrame
のコールバックは、ブラウザがレイアウトを計算し、ピクセルを画面に描画する直前に実行されます。
この同期により、一般的なアニメーションの問題が解消されます:
- 画面の破綻(Screen tearing): フレーム途中での更新による視覚的なアーティファクト
- ジャンク(Jank): 不規則なフレームタイミングによるぎくしゃくした動き
- 無駄なレンダリング: 表示されることのないフレームの描画
リソース効率
requestAnimationFrame
は、ブラウザタブが非アクティブになると自動的に一時停止し、CPUサイクルとバッテリー寿命を節約します。setTimeout
はバックグラウンドタブでも実行を続けますが、ブラウザは約1分後に1秒に1回まで制限する場合があります。
// バッテリー効率の良いアニメーションループ
function gameLoop(timestamp) {
updatePhysics(timestamp);
renderGraphics();
requestAnimationFrame(gameLoop);
}
// setTimeoutによる非効率なアプローチ
function inefficientLoop() {
updatePhysics();
renderGraphics();
setTimeout(inefficientLoop, 16); // 60fpsを試行
}
Discover how at OpenReplay.com.
実用的な使用例
requestAnimationFrameを使用する場面
視覚的更新にはrequestAnimationFrame
を使用します:
- CSSプロパティのアニメーション
- Canvas描画操作
- WebGLレンダリング
- DOM位置の更新
- アニメーション中のプログレスインジケーター
// スムーズスクロールの実装
function smoothScrollTo(targetY, duration) {
const startY = window.scrollY;
const distance = targetY - startY;
const startTime = performance.now();
function scroll(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// よりスムーズな動きのためのイージング関数
const easeInOutQuad = progress * (2 - progress);
window.scrollTo(0, startY + distance * easeInOutQuad);
if (progress < 1) {
requestAnimationFrame(scroll);
}
}
requestAnimationFrame(scroll);
}
setTimeoutを使用する場面
非視覚的タスクにはsetTimeout
を選択します:
- 遅延API呼び出し
- ユーザー入力のデバウンス
- ポーリング操作
- スケジュールされたバックグラウンドタスク
- 一回限りの遅延
// デバウンスされた検索
let searchTimeout;
function handleSearchInput(query) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch(query);
}, 300);
}
// 指数バックオフによるリトライロジック
function fetchWithRetry(url, attempts = 3, delay = 1000) {
return fetch(url).catch(error => {
if (attempts > 1) {
return new Promise(resolve => {
setTimeout(() => {
resolve(fetchWithRetry(url, attempts - 1, delay * 2));
}, delay);
});
}
throw error;
});
}
よくある落とし穴と解決策
フレームレートの前提
requestAnimationFrame
で固定フレームレートを前提にしてはいけません。異なるディスプレイは異なるレートでリフレッシュします(60Hz、120Hz、144Hz)。時間ベースのアニメーションには常にタイムスタンプパラメータを使用してください:
let lastTime = 0;
function animate(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
const element = document.getElementById('moving-element');
const currentLeft = parseFloat(element.style.left) || 0;
// フレームレートに関係なく毎秒100ピクセル移動
const pixelsPerMs = 100 / 1000;
element.style.left = `${currentLeft + pixelsPerMs * deltaTime}px`;
requestAnimationFrame(animate);
}
メモリリーク
コンポーネントがアンマウントされる際は、常にアニメーションフレームIDを保存してクリアしてください:
let animationId;
function startAnimation() {
function animate() {
// アニメーションロジックをここに記述
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
}
function stopAnimation() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
}
// ページアンロード時のクリーンアップ
window.addEventListener('beforeunload', stopAnimation);
迅速な判断ガイド
使用例 | 最適な選択 | 理由 |
---|---|---|
スムーズなアニメーション | requestAnimationFrame | ディスプレイリフレッシュと同期 |
Canvas/WebGLレンダリング | requestAnimationFrame | 破綻とジャンクを防止 |
APIポーリング | setTimeout | 視覚的更新に関連しない |
ユーザー入力のデバウンス | setTimeout | 正確な遅延制御が必要 |
アニメーション中のプログレスバー | requestAnimationFrame | 視覚的フィードバックが必要 |
バックグラウンドデータ処理 | setTimeout | タブが非アクティブでも継続 |
ゲームループ | requestAnimationFrame | 最適なパフォーマンスとバッテリー寿命 |
まとめ
requestAnimationFrame
とsetTimeout
の選択は、どちらが「優れている」かの問題ではなく、適切なツールを適切な用途に使用することです。視覚的更新とアニメーションには、requestAnimationFrame
がブラウザ同期を通じて優れたパフォーマンスを提供します。一般的なタイミングニーズやバックグラウンドタスクには、setTimeout
が必要な柔軟性と予測可能性を提供します。これらの違いを理解することで、JavaScriptアプリケーションがシステムリソースを効率的に管理しながら、スムーズなユーザーエクスペリエンスを提供できるようになります。
よくある質問
16.67msの遅延でsetTimeoutを使用して60fpsを近似することはできますが、ブラウザの実際のリフレッシュサイクルと同期しません。これにより、フレームドロップ、ジャンク、CPUサイクルの無駄が発生します。requestAnimationFrameは自動的にディスプレイのリフレッシュレートに適応します。
はい、requestAnimationFrameはモニターのリフレッシュレートに適応します。120Hzディスプレイでは、約毎秒120回実行されます。異なるディスプレイ間で一貫したアニメーション速度を実現するため、常にタイムスタンプパラメータを使用してデルタタイムを計算してください。
コールバックがフレーム予算を超える場合、ブラウザは応答性を維持するためにフレームをスキップします。これにより目に見えるスタッタリングが発生します。コードの最適化、重い計算にはWeb Workerの使用、またはアニメーションの複雑さの軽減を検討してください。
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.