[javascript] Controlling fps with requestAnimationFrame?

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

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

_x000D_
_x000D_
var ctx = c.getContext("2d"), pTime = 0, mTime = 0, x = 0;_x000D_
ctx.font = "20px sans-serif";_x000D_
_x000D_
// update canvas with some information and animation_x000D_
var fps = new FpsCtrl(12, function(e) {_x000D_
 ctx.clearRect(0, 0, c.width, c.height);_x000D_
 ctx.fillText("FPS: " + fps.frameRate() + _x000D_
                 " Frame: " + e.frame + _x000D_
                 " Time: " + (e.time - pTime).toFixed(1), 4, 30);_x000D_
 pTime = e.time;_x000D_
 var x = (pTime - mTime) * 0.1;_x000D_
 if (x > c.width) mTime = pTime;_x000D_
 ctx.fillRect(x, 50, 10, 10)_x000D_
})_x000D_
_x000D_
// start the loop_x000D_
fps.start();_x000D_
_x000D_
// UI_x000D_
bState.onclick = function() {_x000D_
 fps.isPlaying ? fps.pause() : fps.start();_x000D_
};_x000D_
_x000D_
sFPS.onchange = function() {_x000D_
 fps.frameRate(+this.value)_x000D_
};_x000D_
_x000D_
function FpsCtrl(fps, callback) {_x000D_
_x000D_
 var delay = 1000 / fps,_x000D_
  time = null,_x000D_
  frame = -1,_x000D_
  tref;_x000D_
_x000D_
 function loop(timestamp) {_x000D_
  if (time === null) time = timestamp;_x000D_
  var seg = Math.floor((timestamp - time) / delay);_x000D_
  if (seg > frame) {_x000D_
   frame = seg;_x000D_
   callback({_x000D_
    time: timestamp,_x000D_
    frame: frame_x000D_
   })_x000D_
  }_x000D_
  tref = requestAnimationFrame(loop)_x000D_
 }_x000D_
_x000D_
 this.isPlaying = false;_x000D_
 _x000D_
 this.frameRate = function(newfps) {_x000D_
  if (!arguments.length) return fps;_x000D_
  fps = newfps;_x000D_
  delay = 1000 / fps;_x000D_
  frame = -1;_x000D_
  time = null;_x000D_
 };_x000D_
 _x000D_
 this.start = function() {_x000D_
  if (!this.isPlaying) {_x000D_
   this.isPlaying = true;_x000D_
   tref = requestAnimationFrame(loop);_x000D_
  }_x000D_
 };_x000D_
 _x000D_
 this.pause = function() {_x000D_
  if (this.isPlaying) {_x000D_
   cancelAnimationFrame(tref);_x000D_
   this.isPlaying = false;_x000D_
   time = null;_x000D_
   frame = -1;_x000D_
  }_x000D_
 };_x000D_
}
_x000D_
body {font:16px sans-serif}
_x000D_
<label>Framerate: <select id=sFPS>_x000D_
 <option>12</option>_x000D_
 <option>15</option>_x000D_
 <option>24</option>_x000D_
 <option>25</option>_x000D_
 <option>29.97</option>_x000D_
 <option>30</option>_x000D_
 <option>60</option>_x000D_
</select></label><br>_x000D_
<canvas id=c height=60></canvas><br>_x000D_
<button id=bState>Start/Stop</button>
_x000D_
_x000D_
_x000D_

Old answer

The main purpose of requestAnimationFrame is to sync updates to the monitor's refresh rate. This will require you to animate at the FPS of the monitor or a factor of it (ie. 60, 30, 15 FPS for a typical refresh rate @ 60 Hz).

If you want a more arbitrary FPS then there is no point using rAF as the frame rate will never match the monitor's update frequency anyways (just a frame here and there) which simply cannot give you a smooth animation (as with all frame re-timings) and you can might as well use setTimeout or setInterval instead.

This is also a well known problem in the professional video industry when you want to playback a video at a different FPS then the device showing it refresh at. Many techniques has been used such as frame blending and complex re-timing re-building intermediate frames based on motion vectors, but with canvas these techniques are not available and the result will always be jerky video.

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

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

    ... code for frame here
}

The reason why we place setTimeout first (and why some place rAF first when a poly-fill is used) is that this will be more accurate as the setTimeout will queue an event immediately when the loop starts so that no matter how much time the remaining code will use (provided it doesn't exceed the timeout interval) the next call will be at the interval it represents (for pure rAF this is not essential as rAF will try to jump onto the next frame in any case).

Also worth to note that placing it first will also risk calls stacking up as with setInterval. setInterval may be slightly more accurate for this use.

And you can use setInterval instead outside the loop to do the same.

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

function loop() {

    ... code for frame here
}

And to stop the loop:

clearInterval(rememberMe);

In order to reduce frame rate when the tab gets blurred you can add a factor like this:

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
}

This way you can reduce the FPS to 1/4 etc.

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to performance

Why is 2 * (i * i) faster than 2 * i * i in Java? What is the difference between spark.sql.shuffle.partitions and spark.default.parallelism? How to check if a key exists in Json Object and get its value Why does C++ code for testing the Collatz conjecture run faster than hand-written assembly? Most efficient way to map function over numpy array The most efficient way to remove first N elements in a list? Fastest way to get the first n elements of a List into an Array Why is "1000000000000000 in range(1000000000000001)" so fast in Python 3? pandas loc vs. iloc vs. at vs. iat? Android Recyclerview vs ListView with Viewholder

Examples related to animation

Simple CSS Animation Loop – Fading In & Out "Loading" Text Slidedown and slideup layout with animation How to have css3 animation to loop forever jQuery animated number counter from zero to value CSS Auto hide elements after 5 seconds Fragment transaction animation: slide in and slide out Android Animation Alpha Show and hide a View with a slide up/down animation Controlling fps with requestAnimationFrame? powerpoint loop a series of animation

Examples related to canvas

How to make canvas responsive How to fill the whole canvas with specific color? Use HTML5 to resize an image before upload Convert canvas to PDF Scaling an image to fit on canvas Split string in JavaScript and detect line break Get distance between two points in canvas canvas.toDataURL() SecurityError Converting Chart.js canvas chart to image using .toDataUrl() results in blank image Chart.js canvas resize

Examples related to requestanimationframe

Controlling fps with requestAnimationFrame?