Controlling fps with requestAnimationFrame? Controlling fps with requestAnimationFrame? javascript javascript

Controlling fps with requestAnimationFrame?


How to throttle requestAnimationFrame to a specific frame rate

Demo throttling at 5 FPS: http://jsfiddle.net/m1erickson/CtsY3/

This method works by testing the elapsed time since executing the last frame loop.

Your drawing code executes only when your specified FPS interval has elapsed.

The first part of the code sets some variables used to calculate elapsed time.

var stop = false;var frameCount = 0;var $results = $("#results");var fps, fpsInterval, startTime, now, then, elapsed;// initialize the timer variables and start the animationfunction startAnimating(fps) {    fpsInterval = 1000 / fps;    then = Date.now();    startTime = then;    animate();}

And this code is the actual requestAnimationFrame loop which draws at your specified FPS.

// the animation loop calculates time elapsed since the last loop// and only draws if your specified fps interval is achievedfunction 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    }}


Update 2016/6

The problem throttling the frame rate is that the screen has a constant update rate, typically 60 FPS.

If we want 24 FPS we will never get the true 24 fps on the screen, we can time it as such but not show it as the monitor can only show synced frames at 15 fps, 30 fps or 60 fps (some monitors also 120 fps).

However, for timing purposes we can calculate and update when possible.

You can build all the logic for controlling the frame-rate by encapsulating calculations and callbacks into an object:

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)    }}

Then add some controller and configuration code:

// play statusthis.isPlaying = false;// set frame-ratethis.frameRate = function(newfps) {    if (!arguments.length) return fps;    fps = newfps;    delay = 1000 / fps;    frame = -1;    time = null;};// enable starting/pausing of the objectthis.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;    }};

Usage

It becomes very simple - now, all that we have to do is to create an instance by setting callback function and desired frame rate just like this:

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

Then start (which could be the default behavior if desired):

fc.start();

That's it, all the logic is handled internally.

Demo

var ctx = c.getContext("2d"), pTime = 0, mTime = 0, x = 0;ctx.font = "20px sans-serif";// update canvas with some information and animationvar 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 loopfps.start();// UIbState.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>