requestAnimationFrameでfpsを制御しますか?


140

どうやら requestAnimationFrameは、事実をアニメーション化するための事実上の方法のです。それはほとんどの場合私にとってはうまくいきましたが、今私はいくつかのキャンバスアニメーションを実行しようとしていて、私は疑問に思っていました。私はrAFの目的が一貫してスムーズなアニメーションのためであることを理解しており、アニメーションが途切れる危険を冒す可能性がありますが、今のところ大幅に異なる速度でかなり恣意的に実行されているようです。どういうわけか。

私は使用しますsetIntervalが、rAFが提供する最適化が必要です(特にタブにフォーカスがあると自動的に停止します)。

誰かが私のコードを確認したい場合、それはほとんどです:

animateFlash: function() {
    ctx_fg.clearRect(0,0,canvasWidth,canvasHeight);
    ctx_fg.fillStyle = 'rgba(177,39,116,1)';
    ctx_fg.strokeStyle = 'none';
    ctx_fg.beginPath();
    for(var i in nodes) {
        nodes[i].drawFlash();
    }
    ctx_fg.fill();
    ctx_fg.closePath();
    var instance = this;
    var rafID = requestAnimationFrame(function(){
        instance.animateFlash();
    })

    var unfinishedNodes = nodes.filter(function(elem){
        return elem.timer < timerMax;
    });

    if(unfinishedNodes.length === 0) {
        console.log("done");
        cancelAnimationFrame(rafID);
        instance.animate();
    }
}

ここで、Node.drawFlash()は、カウンター変数に基づいて半径を決定し、円を描くコードです。


1
アニメーションは遅れますか?私の最大の利点requestAnimationFrameは、(名前の種類が示唆するように)必要なときにのみアニメーションフレームを要求することです。静的な黒いキャンバスを表示するとします。新しいフレームは必要ないため、0 fpsになるはずです。ただし、60fpsを必要とするアニメーションを表示している場合は、それも取得する必要があります。rAF不要なフレームを「スキップ」してCPUを節約するだけです。
maxdec 2013年

setIntervalは非アクティブなタブでも機能しません。
ViliusL 2013年

このコードは、90hzディスプレイと60hzディスプレイと144hzディスプレイでは異なる動作をします。
Janthrax

回答:


190

requestAnimationFrameを特定のフレームレートに調整する方法

5 FPSでのデモスロットル: http //jsfiddle.net/m1erickson/CtsY3/

この方法は、最後のフレームループを実行してからの経過時間をテストすることで機能します。

描画コードは、指定したFPS間隔が経過したときにのみ実行されます。

コードの最初の部分は、経過時間の計算に使用されるいくつかの変数を設定します。

var stop = false;
var frameCount = 0;
var $results = $("#results");
var fps, fpsInterval, startTime, now, then, elapsed;


// initialize the timer variables and start the animation

function startAnimating(fps) {
    fpsInterval = 1000 / fps;
    then = Date.now();
    startTime = then;
    animate();
}

そして、このコードは、指定したFPSで描画する実際のrequestAnimationFrameループです。

// the animation loop calculates time elapsed since the last loop
// and only draws if your specified fps interval is achieved

function animate() {

    // request another frame

    requestAnimationFrame(animate);

    // calc elapsed time since last loop

    now = Date.now();
    elapsed = now - then;

    // if enough time has elapsed, draw the next frame

    if (elapsed > fpsInterval) {

        // Get ready for next frame by setting then=now, but also adjust for your
        // specified fpsInterval not being a multiple of RAF's interval (16.7ms)
        then = now - (elapsed % fpsInterval);

        // Put your drawing code here

    }
}

5
優れた説明と例。これは承認済みの回答としてマークする必要があります
muxcmux

13
素敵なデモ-それは受け入れられるべきです。ここでは、Date.now()の代わりにwindow.performance.now()を使用することを示すために、フィドルを分岐しました。:これには、コールバックの内側Date.now()をコールする必要はありません、RAFはすでにrecievesという高解像度のタイムスタンプとうまく行くjsfiddle.net/chicagogrooves/nRpVD/2
ディーン・ラドクリフ

2
新しいrAFタイムスタンプ機能を使用して更新されたリンクをありがとう。新しいrAFタイムスタンプは有用なインフラストラクチャを追加し、Date.nowよりも正確です。
markE 2014

13
これは本当に素晴らしいデモであ​​り、私自身(JSFiddle)を作る気になった。主な違いは、日付の代わりに(ディーンのデモのような)rAFを使用すること、ターゲットフレームレートを動的に調整するためのコントロールを追加すること、アニメーションとは別の間隔でフレームレートをサンプリングすること、および履歴フレームレートのグラフを追加することです。
tavnab

1
制御できるのは、フレームをスキップするときだけです。60 fpsモニターは常に16ms間隔で描画します。たとえば、ゲームを50fpsで実行する場合、6フレームごとにスキップする必要があります。20ミリ秒(1000/50)が経過したかどうかを確認し、経過していない(16ミリ秒しか経過していない)ため、フレームをスキップし、描画してから次のフレームが32ミリ秒経過したので、描画してリセットします。ただし、半分のフレームをスキップして30fpsで実行します。したがって、リセットすると、前回は12ms長く待機しすぎたことを思い出してください。したがって、次のフレームではさらに16ミリ秒が経過しますが、16 + 12 = 28ミリ秒と数えるので、再度ドローして8ミリ秒も長く待機しました
Curtis

47

2016年6月更新

フレームレートを抑制する問題は、画面の更新レートが一定であり、通常は60 FPSであることです。

24 FPSが必要な場合、真の24 fpsを画面に表示することはできません。モニターは15 fps、30 fps、または60 fpsの同期フレームしか表示できないため(モニターによっては120 fpsも表示できるため) )。

ただし、タイミングの目的で、可能な場合は計算して更新できます。

計算とコールバックをオブジェクトにカプセル化することにより、フレームレートを制御するためのすべてのロジックを構築できます。

function FpsCtrl(fps, callback) {

    var delay = 1000 / fps,                               // calc. time per frame
        time = null,                                      // start time
        frame = -1,                                       // frame count
        tref;                                             // rAF time reference

    function loop(timestamp) {
        if (time === null) time = timestamp;              // init start time
        var seg = Math.floor((timestamp - time) / delay); // calc frame no.
        if (seg > frame) {                                // moved to next frame?
            frame = seg;                                  // update
            callback({                                    // callback function
                time: timestamp,
                frame: frame
            })
        }
        tref = requestAnimationFrame(loop)
    }
}

次に、コントローラーと構成コードを追加します。

// play status
this.isPlaying = false;

// set frame-rate
this.frameRate = function(newfps) {
    if (!arguments.length) return fps;
    fps = newfps;
    delay = 1000 / fps;
    frame = -1;
    time = null;
};

// enable starting/pausing of the object
this.start = function() {
    if (!this.isPlaying) {
        this.isPlaying = true;
        tref = requestAnimationFrame(loop);
    }
};

this.pause = function() {
    if (this.isPlaying) {
        cancelAnimationFrame(tref);
        this.isPlaying = false;
        time = null;
        frame = -1;
    }
};

使用法

これは非常に単純になります。今やらなければならないことは、次のようにコールバック関数と必要なフレームレートを設定してインスタンスを作成することだけです。

var fc = new FpsCtrl(24, function(e) {
     // render each frame here
  });

次に開始します(必要に応じて、デフォルトの動作にすることができます)。

fc.start();

それだけです。すべてのロジックは内部で処理されます。

デモ

var ctx = c.getContext("2d"), pTime = 0, mTime = 0, x = 0;
ctx.font = "20px sans-serif";

// update canvas with some information and animation
var fps = new FpsCtrl(12, function(e) {
	ctx.clearRect(0, 0, c.width, c.height);
	ctx.fillText("FPS: " + fps.frameRate() + 
                 " Frame: " + e.frame + 
                 " Time: " + (e.time - pTime).toFixed(1), 4, 30);
	pTime = e.time;
	var x = (pTime - mTime) * 0.1;
	if (x > c.width) mTime = pTime;
	ctx.fillRect(x, 50, 10, 10)
})

// start the loop
fps.start();

// UI
bState.onclick = function() {
	fps.isPlaying ? fps.pause() : fps.start();
};

sFPS.onchange = function() {
	fps.frameRate(+this.value)
};

function FpsCtrl(fps, callback) {

	var	delay = 1000 / fps,
		time = null,
		frame = -1,
		tref;

	function loop(timestamp) {
		if (time === null) time = timestamp;
		var seg = Math.floor((timestamp - time) / delay);
		if (seg > frame) {
			frame = seg;
			callback({
				time: timestamp,
				frame: frame
			})
		}
		tref = requestAnimationFrame(loop)
	}

	this.isPlaying = false;
	
	this.frameRate = function(newfps) {
		if (!arguments.length) return fps;
		fps = newfps;
		delay = 1000 / fps;
		frame = -1;
		time = null;
	};
	
	this.start = function() {
		if (!this.isPlaying) {
			this.isPlaying = true;
			tref = requestAnimationFrame(loop);
		}
	};
	
	this.pause = function() {
		if (this.isPlaying) {
			cancelAnimationFrame(tref);
			this.isPlaying = false;
			time = null;
			frame = -1;
		}
	};
}
body {font:16px sans-serif}
<label>Framerate: <select id=sFPS>
	<option>12</option>
	<option>15</option>
	<option>24</option>
	<option>25</option>
	<option>29.97</option>
	<option>30</option>
	<option>60</option>
</select></label><br>
<canvas id=c height=60></canvas><br>
<button id=bState>Start/Stop</button>

古い答え

の主な目的requestAnimationFrameは、更新をモニターのリフレッシュレートに同期させることです。これには、モニターのFPSまたはその係数(つまり、60 Hzでの典型的なリフレッシュレートでは60、30、15 FPS)でアニメートする必要があります。

さらに任意のFPSが必要な場合は、フレームレートがモニターの更新頻度と一致しないため(ここのフレームのみ)、スムーズなアニメーションを提供できないため(すべてのフレームの再タイミングと同様)、rAFを使用しても意味がありません。 )そしてsetTimeoutsetInterval代わりに使用することもできます。

これは、ビデオを別のFPSで再生するときに、デバイスが更新を表示しているときに、ビデオを専門のビデオ業界でよく知られている問題です。フレームブレンドやモーションベクトルに基づく中間フレームの再構築など、多くの手法が使用されてきましたが、キャンバスではこれらの手法は利用できず、常にぎくしゃくしたビデオになります。

var FPS = 24;  /// "silver screen"
var isPlaying = true;

function loop() {
    if (isPlaying) setTimeout(loop, 1000 / FPS);

    ... code for frame here
}

私たちが最初に置く理由setTimeout (そしてなぜいくつかの場所rAF最初のポリフィルが使用されている)として、これは、より正確になるということであるsetTimeoutループがそう残りのコードが使用されますどのくらいの時間に関係なくすることを開始したときに、すぐにイベントをキューに入れます(タイムアウト間隔を超えない場合)次の呼び出しは、それが表す間隔になります(純粋なrAFの場合、どの場合でもrAFが次のフレームにジャンプしようとするため、これは必須ではありません)。

また、最初に配置すると、呼び出しが次のようにスタックするリスクが生じることにも注意してください。 setIntervalsetIntervalこの使用法の方が少し正確かもしれません。

そして、setInterval代わりにループの外で同じことをすることができます。

var FPS = 29.97;   /// NTSC
var rememberMe = setInterval(loop, 1000 / FPS);

function loop() {

    ... code for frame here
}

ループを停止するには:

clearInterval(rememberMe);

タブがぼやけたときにフレームレートを下げるには、次のような要素を追加します。

var isFocus = 1;
var FPS = 25;

function loop() {
    setTimeout(loop, 1000 / (isFocus * FPS)); /// note the change here

    ... code for frame here
}

window.onblur = function() {
    isFocus = 0.5; /// reduce FPS to half   
}

window.onfocus = function() {
    isFocus = 1; /// full FPS
}

このようにして、FPSを1/4などに減らすことができます。


4
場合によっては、モニターのフレームレートを一致させようとせず、たとえば、画像シーケンスでドロップフレームを一致させようとします。優れた説明
twtw

3
requestAnimationFrameで調整する最大の理由の1つは、ブラウザのアニメーションフレームにコードの実行を合わせることです。特に音楽ビジュアライザなどのように、フレームごとにデータにロジックを実行している場合は特に、スムーズに実行されます。
Chris Dolphin

4
の主な用途はrequestAnimationFrameDOM操作の同期(読み取り/書き込み)であるため、これを使用しないと、DOMにアクセスするときにパフォーマンスが低下します。操作が同時に実行されるためにキューに入れられず、レイアウトが不必要に再描画されるためです。
vsync 2015

1
JavaScriptはシングルスレッドで実行され、コードの実行中にタイムアウトイベントはトリガーされないため、「呼び出しがスタックする」リスクはありません。したがって、関数がタイムアウトよりも長くかかる場合、ブラウザは可能な限り高速で実行されますが、ブラウザは再描画を行い、呼び出しの間に他のタイムアウトをトリガーします。
dronus

ページの更新はディスプレイのfps制限よりも速く更新できないとおっしゃっていました。しかし、ページのリフローをトリガーすることでより速く更新することは可能ですか?逆に、ネイティブのfpsレートよりも速く実行された場合、複数のページのリフローに気付かない可能性はありますか?
Travis J

36

への呼び出しをrequestAnimationFrameで囲むことをお勧めしますsetTimeoutsetTimeoutアニメーションフレームを要求した関数内から呼び出すと、の目的が無効になりますrequestAnimationFrame。しかしrequestAnimationFrame、内部から呼び出すとsetTimeoutスムーズに動作します。

var fps = 25
function animate() {
  setTimeout(function() {
    requestAnimationFrame(animate);
  }, 1000 / fps);
}

1
これは実際にはフレームレートを低く保つために機能し、CPUを料理しないように見えます。そして、それはとても簡単です。乾杯!
フォックス2017

これは、軽量のアニメーションでこれを行うための素晴らしい、簡単な方法です。ただし、少なくとも一部のデバイスでは、同期が少しずれます。以前のエンジンの1つでこのテクニックを使用しました。物事が複雑になるまでうまくいきました。最大の問題は、方向センサーに接続したときに遅れる、またはぐらつくことでした。後で、別のsetIntervalを使用し、センサー、setIntervalフレーム、およびRAFフレーム間でオブジェクトプロパティを介して更新を通信することで、センサーとRAFをリアルタイムで移動できるようになり、アニメーション間隔は、setIntervalからのプロパティ更新を介して制御できるようになりました。
jdmayfield 2018年

ベストアンサー!ありがとう;)
538ROMEO 2018

私のモニターは60 FPSです。varfps = 60を設定した場合、このコードを使用して取得できるのは約50 FPSだけです。120 FPSモニターを持っている人もいるので、60に遅くしたいのですが、他のすべての人に影響を与えたくありません。これは意外と難しいです。
カーティス、

FPSが予想よりも低くなるのは、setTimeoutが指定された遅延を超えてコールバックを実行できるためです。これにはいくつかの理由が考えられます。また、ループごとに新しいタイマーを設定し、新しいタイムアウトを設定する前にコードを実行するのに時間がかかります。これを正確にする方法はありません。常に予想よりも遅い結果を考慮する必要がありますが、どれほど遅くなるかわからない限り、遅延を小さくしようとしても不正確になります。ブラウザのJSはそれほど正確であるように意図されていません。
pdepmcp

17

これらは、深く掘り下げるまで、理論的にはすべて良いアイデアです。 問題は、RAFを非同期化せずにRAFをスロットルすることができず、それを無効にすることが既存の目的であるということです。 あなたはそれがフルスピードで実行し、別のループでデータを更新しましょうだからあるいは別のスレッド!

はい、私はそれを言った。あなたはできるブラウザでマルチスレッドJavaScriptを行います!

私が知っている2つの方法は、ジャンクなしで非常にうまく機能することです。はるかに少ないジュースを使用し、発熱を抑えます。正確な人間規模のタイミングと機械効率が最終的な結果です。

これは少し冗長ですが、ここに行きます...


方法1:setIntervalを介してデータを更新し、RAFを介してグラフィックを更新します。

移動と回転の値、物理、衝突などを更新するには、個別のsetIntervalを使用します。アニメーション化された要素ごとに、これらの値をオブジェクトに保持します。各setInterval 'frame'のオブジェクトの変数に変換文字列を割り当てます。これらのオブジェクトを配列に保持します。間隔をmsで希望するfpsに設定します:ms =(1000 / fps)。これにより、RAF速度に関係なく、任意のデバイスで同じfpsを可能にする安定したクロックが維持されます。 ここで要素に変換を割り当てないでください!

requestAnimationFrameループで、古い学校のforループを使用して配列を反復処理します。ここでは新しいフォームを使用しないでください。低速です。

for(var i=0; i<sprite.length-1; i++){  rafUpdate(sprite[i]);  }

rafUpdate関数で、配列内のjsオブジェクトから変換文字列を取得し、その要素IDを取得します。変数にアタッチされているか、他の方法で簡単にアクセスできる「スプライト」要素がすでにあるはずなので、RAFで「取得」する時間を無駄にしないでください。それらのhtml idにちなんで名付けられたオブジェクトにそれらを保持することはかなりうまくいきます。SIまたはRAFに入る前に、その部分をセットアップします。

RAFを使用して、変換のみを更新し、3D変換のみ(2dの場合でも)を使用し、cssに "will-change:transform;"を設定します。変化する要素について。これにより、変換がネイティブリフレッシュレートに可能な限り同期され、GPUが起動し、ブラウザーに最も集中する場所を指示します。

したがって、この疑似コードのようなものが必要です...

// refs to elements to be transformed, kept in an array
var element = [
   mario: document.getElementById('mario'),
   luigi: document.getElementById('luigi')
   //...etc.
]

var sprite = [  // read/write this with SI.  read-only from RAF
   mario: { id: mario  ....physics data, id, and updated transform string (from SI) here  },
   luigi: {  id: luigi  .....same  }
   //...and so forth
] // also kept in an array (for efficient iteration)

//update one sprite js object
//data manipulation, CPU tasks for each sprite object
//(physics, collisions, and transform-string updates here.)
//pass the object (by reference).
var SIupdate = function(object){
  // get pos/rot and update with movement
  object.pos.x += object.mov.pos.x;  // example, motion along x axis
  // and so on for y and z movement
  // and xyz rotational motion, scripted scaling etc

  // build transform string ie
  object.transform =
   'translate3d('+
     object.pos.x+','+
     object.pos.y+','+
     object.pos.z+
   ') '+

   // assign rotations, order depends on purpose and set-up. 
   'rotationZ('+object.rot.z+') '+
   'rotationY('+object.rot.y+') '+
   'rotationX('+object.rot.x+') '+

   'scale3d('.... if desired
  ;  //...etc.  include 
}


var fps = 30; //desired controlled frame-rate


// CPU TASKS - SI psuedo-frame data manipulation
setInterval(function(){
  // update each objects data
  for(var i=0; i<sprite.length-1; i++){  SIupdate(sprite[i]);  }
},1000/fps); //  note ms = 1000/fps


// GPU TASKS - RAF callback, real frame graphics updates only
var rAf = function(){
  // update each objects graphics
  for(var i=0; i<sprite.length-1; i++){  rAF.update(sprite[i])  }
  window.requestAnimationFrame(rAF); // loop
}

// assign new transform to sprite's element, only if it's transform has changed.
rAF.update = function(object){     
  if(object.old_transform !== object.transform){
    element[object.id].style.transform = transform;
    object.old_transform = object.transform;
  }
} 

window.requestAnimationFrame(rAF); // begin RAF

これにより、SIで目的の「フレーム」レートに同期されたデータオブジェクトと変換文字列への更新、およびGPUリフレッシュレートに同期されたRAFでの実際の変換割り当てが維持されます。そのため、実際のグラフィックスの更新はRAFでのみ行われますが、データへの変更と変換文字列の作成はSIで行われるため、ジャンキーではなく、希望のフレームレートで「時間」が流れます。


フロー:

[setup js sprite objects and html element object references]

[setup RAF and SI single-object update functions]

[start SI at percieved/ideal frame-rate]
  [iterate through js objects, update data transform string for each]
  [loop back to SI]

[start RAF loop]
  [iterate through js objects, read object's transform string and assign it to it's html element]
  [loop back to RAF]

方法2. SIをWebワーカーに配置します。これはFAAASTでスムーズです!

方法1と同じですが、SIをWebワーカーに配置します。その後、完全に別のスレッドで実行され、ページはRAFとUIのみを処理します。スプライト配列を「転送可能なオブジェクト」としてやり取りします。これは高速ブコです。複製やシリアライズに時間はかかりませんが、反対側からの参照が破棄されるという点で参照による受け渡しとは異なります。そのため、両側を反対側に渡し、存在する場合にのみそれらを更新する必要があります。高校でガールフレンドとノートをやり取りするようなものです。

一度に読み書きできるのは1人だけです。エラーを回避するために未定義でないかどうかを確認する限り、これは問題ありません。RAFは高速で、すぐにキックバックし、GPUフレームの束を調べて、まだ送り返されているかどうかを確認します。ほとんどの場合、WebワーカーのSIはスプライト配列を持ち、位置、動き、物理データを更新し、新しい変換文字列を作成して、ページのRAFに返します。

これは、スクリプトを使用して要素をアニメーション化するための最も速い方法です。2つの関数は、2つの別個のプログラムとして、2つの別個のスレッドで実行され、マルチコアCPUを活用することで、単一のjsスクリプトでは実現できません。マルチスレッドのJavaScriptアニメーション。

そして、それはジャンクなしでスムーズに行いますが、実際の指定されたフレームレートで、ほとんど発散しません。


結果:

これらの2つの方法のいずれかを使用すると、任意のPC、電話、タブレットなどで(もちろん、デバイスとブラウザーの機能内で)スクリプトが同じ速度で実行されます。


補足として、方法1では、setIntervalのアクティビティが多すぎると、シングルスレッドの非同期により、RAFが遅くなる可能性があります。SIフレーム以上にそのアクティビティを分割することでこれを緩和できるため、非同期はより迅速にRAFに制御を渡します。RAFは最大フレームレートで動作しますが、グラフィックの変更をディスプレイと同期するため、いくつかのRAFフレームをスキップしても問題ありません。SIフレームを超えてスキップしない限り、ジャンクしません。
jdmayfield 2018年

方法2は、2つのループを実際にマルチタスクで処理し、非同期で前後に切り替えないため、より堅牢ですが、SIフレームが希望のフレームレートよりも長くかかることを避けたいため、SIアクティビティの分割は依然として可能です。大量のデータ操作が行われている場合は、完了するのに複数のSIフレームが必要になります。
jdmayfield 2018年

興味深いことに、このようなペアのループを実行すると、GPUがsetIntervalループで指定されたフレームレートで実行されていることがChromes DevToolsに実際に登録されることに言及する価値があると思いました。グラフィックの変化が発生するRAFフレームのみがFPSメーターによってフレームとしてカウントされるように見えます。したがって、グラフィック以外の作業のみ、または空白のループのみのRAFフレームは、GPUに関する限りカウントされません。これは、今後の研究の出発点として興味深いものです。
jdmayfield

このソリューションには、たとえばユーザーが別のタブに切り替えたために、rAFが中断されたときに実行し続けるという問題があると思います。
N4ppeL

1
PS私はいくつかの読書をしました、そしてそれはほとんどのブラウザーがとにかくバックグラウンドタブで時間イベントを毎秒1回に制限しているようです(おそらく何らかの方法で処理されるべきです)。それでも問題に対処し、表示されなくなったときに完全に一時停止したい場合は、visibilitychangeイベントがあるようです。
N4ppeL

3

特定のFPSに簡単に調整する方法:

// timestamps are ms passed since document creation.
// lastTimestamp can be initialized to 0, if main loop is executed immediately
var lastTimestamp = 0,
    maxFPS = 30,
    timestep = 1000 / maxFPS; // ms for each frame

function main(timestamp) {
    window.requestAnimationFrame(main);

    // skip if timestep ms hasn't passed since last frame
    if (timestamp - lastTimestamp < timestep) return;

    lastTimestamp = timestamp;

    // draw frame here
}

window.requestAnimationFrame(main);

出典:Isaac SukinによるJavaScriptゲームのループとタイミングの詳細な説明


1
モニターが60 FPSで実行され、ゲームを58 FPSで実行したい場合、maxFPS = 58に設定すると、2番目のフレームごとにスキップされるため、30 FPSで実行されます。
カーティス、

はい、これも試しました。私は実際にはRAF自体を抑制しないことを選択します。変更のみがsetTimeoutによって更新されます。少なくともChromeでは、DevToolsの読み取りによると、これにより、有効なfpsがsetTimeoutsのペースで実行されます。もちろん、実際のビデオフレームはビデオカードの速度で更新し、リフレッシュレートを監視することしかできませんが、この方法はジャンキーが最も少ないため、最も滑らかな「見かけの」fpsコントロールで動作しているようで、これが目的です。
jdmayfield

私は、JSオブジェクトのすべてのモーションをRAFとは別に追跡しているため、RAFやsetTimeoutに関係なく、少し余分な計算を行うだけで、アニメーションロジック、衝突検出など、必要なものを知覚的に一貫した速度で実行できます。
jdmayfield

2

スキップrequestAnimationFrameのが原因スムーズないカスタムfpsで(希望)のアニメーションを。

// Input/output DOM elements
var $results = $("#results");
var $fps = $("#fps");
var $period = $("#period");

// Array of FPS samples for graphing

// Animation state/parameters
var fpsInterval, lastDrawTime, frameCount_timed, frameCount, lastSampleTime, 
		currentFps=0, currentFps_timed=0;
var intervalID, requestID;

// Setup canvas being animated
var canvas = document.getElementById("c");
var canvas_timed = document.getElementById("c2");
canvas_timed.width = canvas.width = 300;
canvas_timed.height = canvas.height = 300;
var ctx = canvas.getContext("2d");
var ctx2 = canvas_timed.getContext("2d");


// Setup input event handlers

$fps.on('click change keyup', function() {
    if (this.value > 0) {
        fpsInterval = 1000 / +this.value;
    }
});

$period.on('click change keyup', function() {
    if (this.value > 0) {
        if (intervalID) {
            clearInterval(intervalID);
        }
        intervalID = setInterval(sampleFps, +this.value);
    }
});


function startAnimating(fps, sampleFreq) {

    ctx.fillStyle = ctx2.fillStyle = "#000";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx2.fillRect(0, 0, canvas.width, canvas.height);
    ctx2.font = ctx.font = "32px sans";
    
    fpsInterval = 1000 / fps;
    lastDrawTime = performance.now();
    lastSampleTime = lastDrawTime;
    frameCount = 0;
    frameCount_timed = 0;
    animate();
    
    intervalID = setInterval(sampleFps, sampleFreq);
		animate_timed()
}

function sampleFps() {
    // sample FPS
    var now = performance.now();
    if (frameCount > 0) {
        currentFps =
            (frameCount / (now - lastSampleTime) * 1000).toFixed(2);
        currentFps_timed =
            (frameCount_timed / (now - lastSampleTime) * 1000).toFixed(2);
        $results.text(currentFps + " | " + currentFps_timed);
        
        frameCount = 0;
        frameCount_timed = 0;
    }
    lastSampleTime = now;
}

function drawNextFrame(now, canvas, ctx, fpsCount) {
    // Just draw an oscillating seconds-hand
    
    var length = Math.min(canvas.width, canvas.height) / 2.1;
    var step = 15000;
    var theta = (now % step) / step * 2 * Math.PI;

    var xCenter = canvas.width / 2;
    var yCenter = canvas.height / 2;
    
    var x = xCenter + length * Math.cos(theta);
    var y = yCenter + length * Math.sin(theta);
    
    ctx.beginPath();
    ctx.moveTo(xCenter, yCenter);
    ctx.lineTo(x, y);
  	ctx.fillStyle = ctx.strokeStyle = 'white';
    ctx.stroke();
    
    var theta2 = theta + 3.14/6;
    
    ctx.beginPath();
    ctx.moveTo(xCenter, yCenter);
    ctx.lineTo(x, y);
    ctx.arc(xCenter, yCenter, length*2, theta, theta2);

    ctx.fillStyle = "rgba(0,0,0,.1)"
    ctx.fill();
    
    ctx.fillStyle = "#000";
    ctx.fillRect(0,0,100,30);
    
    ctx.fillStyle = "#080";
    ctx.fillText(fpsCount,10,30);
}

// redraw second canvas each fpsInterval (1000/fps)
function animate_timed() {
    frameCount_timed++;
    drawNextFrame( performance.now(), canvas_timed, ctx2, currentFps_timed);
    
    setTimeout(animate_timed, fpsInterval);
}

function animate(now) {
    // request another frame
    requestAnimationFrame(animate);
    
    // calc elapsed time since last loop
    var elapsed = now - lastDrawTime;

    // if enough time has elapsed, draw the next frame
    if (elapsed > fpsInterval) {
        // Get ready for next frame by setting lastDrawTime=now, but...
        // Also, adjust for fpsInterval not being multiple of 16.67
        lastDrawTime = now - (elapsed % fpsInterval);

        frameCount++;
    		drawNextFrame(now, canvas, ctx, currentFps);
    }
}
startAnimating(+$fps.val(), +$period.val());
input{
  width:100px;
}
#tvs{
  color:red;
  padding:0px 25px;
}
H3{
  font-weight:400;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3>requestAnimationFrame skipping <span id="tvs">vs.</span> setTimeout() redraw</h3>
<div>
    <input id="fps" type="number" value="33"/> FPS:
    <span id="results"></span>
</div>
<div>
    <input id="period" type="number" value="1000"/> Sample period (fps, ms)
</div>
<canvas id="c"></canvas><canvas id="c2"></canvas>

@tavnabによるオリジナルコード。


2
var time = 0;
var time_framerate = 1000; //in milliseconds

function animate(timestamp) {
  if(timestamp > time + time_framerate) {
    time = timestamp;    

    //your code
  }

  window.requestAnimationFrame(animate);
}

あなたのコードが何をしているかを説明するためにいくつかの文を追加してください。そうすれば、あなたの答えに対するより多くの賛成を得られるようになります。
ファジー分析

1

私はいつもタイムスタンプをいじることなくこれを非常に簡単な方法で行います:

var fps, eachNthFrame, frameCount;

fps = 30;

//This variable specifies how many frames should be skipped.
//If it is 1 then no frames are skipped. If it is 2, one frame 
//is skipped so "eachSecondFrame" is renderd.
eachNthFrame = Math.round((1000 / fps) / 16.66);

//This variable is the number of the current frame. It is set to eachNthFrame so that the 
//first frame will be renderd.
frameCount = eachNthFrame;

requestAnimationFrame(frame);

//I think the rest is self-explanatory
fucntion frame() {
  if (frameCount == eachNthFrame) {
    frameCount = 0;
    animate();
  }
  frameCount++;
  requestAnimationFrame(frame);
}

1
モニターが120 fpsの場合、これは速すぎます。
カーティス

0

これは私が見つけた良い説明です:CreativeJS.com、setTimeou)呼び出しをrequestAnimationFrameに渡された関数内にラップします。「プレーン」なrequestionAnimationFrameに対する私の懸念は、「1秒間に3回だけアニメーション化したい場合はどうなりますか?」(setTimeoutではなく)requestAnimationFrameを使用しても、、「エネルギー」(ブラウザのコードが何かを実行していることを意味し、システムの速度が低下する可能性がある)を60秒または120回、または1秒に何回無駄にします。 1秒間に2〜3回しかやりません(必要に応じて)。

ほとんどの場合、私はこの理由のためにJavaScriptを使用してブラウザーを意図的にオフにして実行します。しかし、私はYosemite 10.10.3を使用していて、少なくとも私の古いシステム(比較的古い-2011を意味する)では、何らかのタイマーの問題があると思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.