11

What is the best way to get the time difference between "window.requestAnimationFrame" callback in javascript?

I have tried:

// create the best .requestAnimationFrame callback for each browser
window.FPS = (function() {
    return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
    function(callback) {window.setTimeout(callback, 1000 / 60);};
})();

// start animation loop
var dt, stamp = (new Date()).getTime();
function loop() {
    window.FPS(loop);
    var now = (new Date()).getTime();
    var dt = now - stamp;
    stamp = now;
}
// has "dt" the best accuracy?
marirena
  • 300
  • 2
  • 10
  • 1
    potential duplicate: http://stackoverflow.com/questions/8279729/calculate-fps-in-canvas-using-requestanimationframe – Norman Breau Apr 30 '15 at 02:15
  • Fairly sure that the callback is passed a single parameter, a timestamp (in full native requestAnimationFrame implementations) if your looking for support for the polyfill, there are ones out there that emulate the timestamp parameter better than what you have for your window.FPS polyfill. [This](https://gist.github.com/timhall/4078614) is perhaps one of the better ones I've seen. Related to [this](http://stackoverflow.com/questions/13241314/up-to-date-polyfill-for-requestanimationframe) SO question – OJay Apr 30 '15 at 02:24
  • 1
    Btw, you can completely omit that IEFE and its `return`, just assign `window.raf = … || … || function(cb){…};` – Bergi Apr 30 '15 at 02:34

4 Answers4

6

Most modern browsers automatically send in a high-precision timestamp as an argument into each requestAnimation callback loop: http://caniuse.com/#search=performance

So you simply subtract the last timestamp from the current timestamp to get the elapsed time since the loop was last run.

Here's example code and a Demo:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

var startingTime;
var lastTime;
var totalElapsedTime;
var elapsedSinceLastLoop;

var $total=$('#total');
var $loop=$('#loop');

requestAnimationFrame(loop);

function loop(currentTime){
  if(!startingTime){startingTime=currentTime;}
  if(!lastTime){lastTime=currentTime;}
  totalElapsedTime=(currentTime-startingTime);
  elapsedSinceLastLoop=(currentTime-lastTime);
  lastTime=currentTime;
  $total.text('Since start: '+totalElapsedTime+' ms');
  $loop.text('Since last loop: '+elapsedSinceLastLoop+' ms');
  requestAnimationFrame(loop);
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<p id=total>T1</p>
<p id=loop>T2</p>
<canvas id="canvas" width=300 height=300></canvas>

For the couple of browsers that don't support Performance, you will have to use Date.now() instead of currentTime inside the loop since no timestamp is automatically sent by those browsers into the loop.

markE
  • 102,905
  • 11
  • 164
  • 176
5

has "dt" the best accuracy?

No. According to the docs,

The callback method is passed a single argument, a DOMHighResTimeStamp, which indicates the current time when callbacks queued by requestAnimationFrame begin to fire

so you should rather use that to get high precision.

function loop(now) {
    var last = now || Date.now(); // fallback if no precise time is given
    window.FPS(function(now) {
        now = now || Date.now();
        var dt = now - last;
        if (dt != 0) // something might be wrong with our frames
             console.log(dt);
        loop(now);
    });
}
window.FPS(loop);

(jsfiddle demo)

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Also worth to notice is that the rAF timestamp will be somewhat quantized in any case to either 16.67ms, 16.67x2 etc. as it's synched to the monitor refresh rate. –  Apr 30 '15 at 03:49
  • var last = now || Date.now() is wrong i think. "now" is the timestamp since animationFrame started, while Date.now() is Unix timestamp – marirena Apr 30 '15 at 12:24
  • @marirena: No, `now` "*indicates the current time*" according to the docs, not the timespan since the invocation of rAF. It's an absolute milliseconds timestamp just as `Date.now`, but with greater accuracy (and possibly a different origin). You can completely omit the `|| …` part as well, it's just a fallback for incompatible rAF shims. – Bergi Apr 30 '15 at 12:29
  • i just ran it with console.log(now) and "now" reads 0 at start and increases by 1000 per second – marirena Apr 30 '15 at 12:31
  • @marirena: Just as I said. It might have its origin at the page load instead of 1/1/1970, but it's a millisecond timestamp nonetheless. And since we only care for deltas, the origin doesn't matter. – Bergi Apr 30 '15 at 12:46
2

I will write a clear conclusion, for everyone who wants to use this pattern

// CREATING AN FPS ENGINE

window.FPS = (function() {
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(callback) {window.setTimeout(callback, 1000 / 60);};
})();

var FPS = {
    loop: function(canvas_object) { // OPTIONAL canvas_object, I think it increases performance | canvas_object = document.getElementById("canvas_id")
        var ticks = window.FPS(function(now){
            var dt = now - FPS.stamp || 0;
            FPS.stamp = now;
            FPS.update(dt, FPS.stamp, ticks);
            FPS.loop(canvas_object);
        }, canvas_object);

    },
    update: undefined,
    stamp: undefined
};

// USING THE FPS ENGINE

FPS.loop(the_canvas_object); // starts the engine
FPS.update = function(dt, stamp, ticks) {
    // The game/video loop, using accurate dt. Stamp is the time since engine started. Ticks is the number of the loop cycles
    console.log("dt: " + dt + ", Stamp: " + stamp + ", Ticks: " + ticks); // check output   
    // HAPPY GAME CREATING
    var fps= (1 / (dt / 1000)).toFixed(1);
};
marirena
  • 300
  • 2
  • 10
  • 2
    `(1 / (dt / 1000))` can be simplified to this: `(1000 / dt)`. Don't know why you did it this way. – Al.G. Dec 13 '15 at 09:12
0
your_module.prototype.log_frame_speed=function(){
    this.cframe++;
    if(this.cframe>10000)  {sys.exit();}

    this.date_now=Date.now();
    this.date_delta=this.date_now-this.date_previous;
    this.date_previous=this.date_now;
    //console.log(this.date_delta);
    };  

your_module.prototype.implement_next_frame=function(){
    // code update.                                                                                                 
    // render update.
    this.log_frame_speed();

    requestAnimationFrame(this.implement_next_frame.bind(this));
    }; 
john-jones
  • 7,490
  • 18
  • 53
  • 86