Create a gist now

Instantly share code, notes, and snippets.

@remy /_README.md
Last active Nov 18, 2017

What would you like to do?
requestAnimationFrame helper

raf.js

A simple script with a few niceties that allows for multiple requestAnimationFrame calls, and FPS pinning.

How it works

The script polyfills rAF if required, then overloads requestAnimationFrame and cancelAnimationFrame with a process that allows multiple frames to be queued up for rAF to run.

This is useful if there are multiple animations running on the page, you want all the callbacks to happen at once, and not on multiple rAF calls. This script is meant as a drop-in solution to that problem.

API

By default it will overload the original requestAnimationFrame methods, but if you remove the two arguments at the end of the script (or change them) it will introduce window.raf (or what you've named the request & cancel functions).

  • requestAnimationFrame(fn): can be called multiple times at once, and the queue will only be cleared when the real rAF callback fires
  • requestAnimationFrame.cancel: helper to cancelAnimationFrame - returns true if a handler was due to fire.
  • requestAnimationFrame.fps(fn, fps): helper to pin you function to run every N frames per second. fps is not milliseconds. Note that this is more akin to setInterval than rAF, as it will reschedule your function to run every N frames.
  • requestAnimationFrame.running: boolean flag to pause all rAF calls - set to false and animations will stop, set to true and they'll resume.
(function(window, request, cancel) {
'use strict';
var lastTime = 0;
var strings = {
raf: 'requestAnimationFrame',
caf: 'cancelAnimationFrame',
af: 'AnimationFrame',
};
/**
* requestAnimationFrame shim layer with setTimeout fallback
* @see http://paulirish.com/2011/requestanimationframe-for-smart-animating
* modified for use with raf shim and raf based setInterval/Timeout
*/
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window[strings.raf]; ++x) {
window[strings.raf] = window[vendors[x]+'Request' + strings.af];
window[strings.caf] =
window[vendors[x]+'Cancel' + strings.af] || window[vendors[x]+'CancelRequest' + strings.af];
}
if (!window[strings.raf]) {
window[strings.raf] = function(callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window[strings.caf]) {
window[strings.caf] = function(id) {
window.clearTimeout(id);
};
}
/* ^---------- end of polyfill, start of raf shim ------------> */
var queue = [];
var lookup = {};
var guid = 0;
var _rAF = window[strings.raf];
var _cAF = window[strings.caf];
function clearQueue(time) {
_rAF(clearQueue);
if (!queue.length || !raf.running) {
return;
}
var todo = queue.splice(0); // move items across
var i = 0;
var item = null;
var length = todo.length;
for (; i < length; i++) {
item = todo[i];
if (!item.cancel) {
item.fn(time);
}
delete lookup[item.guid];
}
}
clearQueue();
var raf = function raf(fn) {
guid++;
lookup[guid] = fn;
queue.push({
guid: guid,
cancel: false,
fn: fn,
});
return guid;
};
raf.running = true;
raf.cancel = function (id) {
if (lookup[id]) {
lookup[id].cancel = true;
return true;
}
return false;
};
raf.fps = function (fn, fps) {
var lastFrameTime = 0;
var ms = 1000 / fps;
function update(elapsedTime) {
// calculate the delta since the last frame
var delta = elapsedTime - (lastFrameTime || 0);
// queue up an rAF update call
raf(update);
// if we *don't* already have a first frame, and the
// delta is less than 33ms (30fps in this case) then
// don't do anything and return
if (lastFrameTime && delta < ms) {
return;
}
// else we have a frame we want to update at our fps...
// capture the last frame update time so we can work out
// a delta next time.
lastFrameTime = elapsedTime;
// now do the frame update and render work
fn(elapsedTime);
}
update();
};
if (cancel) {
window[cancel] = raf.cancel;
}
if (!request) {
request = 'raf';
}
window[request] = raf;
})(window, 'requestAnimationFrame', 'cancelAnimationFrame');
!function(a,b,c){"use strict";function m(a){if(k(m),h.length&&n.running)for(var b=h.splice(0),c=0,d=null,e=b.length;e>c;c++)d=b[c],d.cancel||d.fn(a),delete i[d.guid]}for(var d=0,e={raf:"requestAnimationFrame",caf:"cancelAnimationFrame",af:"AnimationFrame"},f=["ms","moz","webkit","o"],g=0;g<f.length&&!a[e.raf];++g)a[e.raf]=a[f[g]+"Request"+e.af],a[e.caf]=a[f[g]+"Cancel"+e.af]||a[f[g]+"CancelRequest"+e.af];a[e.raf]||(a[e.raf]=function(b){var c=(new Date).getTime(),e=Math.max(0,16-(c-d)),f=a.setTimeout(function(){b(c+e)},e);return d=c+e,f}),a[e.caf]||(a[e.caf]=function(b){a.clearTimeout(b)});var h=[],i={},j=0,k=a[e.raf];a[e.caf],m();var n=function(a){return j++,i[j]=a,h.push({guid:j,cancel:!1,fn:a}),j};n.running=!0,n.cancel=function(a){return i[a]?(i[a].cancel=!0,!0):!1},n.fps=function(a,b){function e(b){var f=b-(c||0);n(e),c&&d>f||(c=b,a(b))}var c=0,d=1e3/b;e()},c&&(a[c]=n.cancel),b||(b="raf"),a[b]=n}(window,"requestAnimationFrame","cancelAnimationFrame");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment