Skip to content

Instantly share code, notes, and snippets.

@nicoburns
Created October 1, 2013 01:37
Show Gist options
  • Select an option

  • Save nicoburns/6772816 to your computer and use it in GitHub Desktop.

Select an option

Save nicoburns/6772816 to your computer and use it in GitHub Desktop.
renderloop: A simple requestAnimationFrame shim and wrapper for easy animating.
// Core module: renderloop v0.2.0dev
define([
"lodash"
], function (_) {
var RenderLoop = {
timer: null,
queue: [],
persistentTasks: [],
options: {
auto: true
},
lastProcessTime: null,
deltaTime: 0,
fps: 0
};
// performance.now() shim
(function() {
var performance = window.performance || {};
var now = performance.now ||
performance.webkitNow ||
performance.msNow ||
performance.oNow ||
performance.mozNow;
// Precise timing is available: use it
if (now) {
RenderLoop.usingHighResTiming = true;
RenderLoop.now = function () {
return now.call(performance);
};
}
// Else fallback to Date.now()
else {
var scriptLoadTime = +new Date();
RenderLoop.usingHighResTiming = false;
RenderLoop.now = function() {
return +new Date() - scriptLoadTime;
};
}
})();
// requestAnimationFrame implementation as a custom function to allow blacklisting
// devices with broken implementation. Currently needs timer-based fallbacks for iOS 6.x for
// code running inside <iframe> elements.
// Uses polyfills based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
(function() {
var blacklisted = /iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent),
lastTime = 0,
nativeRequest = window.requestAnimationFrame || null,
nativeCancel = window.cancelAnimationFrame || null;
['webkit', 'moz'].forEach(function(prefix) {
nativeRequest = nativeRequest || window[prefix+'RequestAnimationFrame'] || null;
nativeCancel = nativeCancel || window[prefix+'CancelAnimationFrame'] || window[prefix+'CancelRequestAnimationFrame'] || null;
});
function polyfillRequest(callback, element) {
var currTime = RenderLoop.now();//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;
}
function polyfillCancel(id) {
clearTimeout(id);
}
RenderLoop.nextFrame = (!blacklisted && nativeRequest !== null) ? _.bind(nativeRequest, window) : polyfillRequest;
RenderLoop.cancelFrame = (!blacklisted && nativeCancel !== null) ? _.bind(nativeCancel, window) : polyfillCancel;
})();
RenderLoop.enqueue = function (callback, context, args) {
var arrayFunc = "push";
// Allow for 'unshift' like behaviour
if (callback === "atstart") {
arrayFunc = "unshift";
callback = context;
context = args;
args = arguments[3];
}
// Ensure that callback is a function
if (!_.isFunction(callback)) return;
// Ensure that args is an array
args = _.isArray(args) ? args : [args];
// Push item to queue
RenderLoop.queue[ arrayFunc ]({
"callback": callback,
"context": context,
"args": args || []
});
// If auto-processing is on, then start the queue (no-op if already started)
if (RenderLoop.options.auto) RenderLoop.start();
};
RenderLoop.addPersistentTask = function (name, callback, context, args) {
RenderLoop.persistentTasks.push({
"name": name,
"callback": callback,
"context": context,
"args": args || []
});
// If auto-processing is on, then start the queue (no-op if already started)
if (RenderLoop.options.auto) RenderLoop.start();
};
RenderLoop.removePersistentTask = function (name) {
RenderLoop.persistentTasks = _(RenderLoop.persistentTasks).filter(function(task) {
return (task.name !== name);
});
};
RenderLoop.clear = function () {
RenderLoop.queue = [];
};
// Start processing the queue
RenderLoop.start = function () {
if (RenderLoop.timer === null) {
RenderLoop.timer = RenderLoop.nextFrame( RenderLoop.process );
}
};
// Stop processing the queue
RenderLoop.stop = function () {
if (RenderLoop.timer) {
RenderLoop.cancelFrame( RenderLoop.timer );
RenderLoop.timer = null;
}
};
RenderLoop.process = function (timestamp) {
// Exit early if task queue is empty
if (RenderLoop.queue.length < 1 && RenderLoop.persistentTasks.length < 1) {
return RenderLoop.stop();
}
// Normalise time for older implementations of RAF which pass in Date.now() instead of performance.now()
if (RenderLoop.usingHighResTiming && timestamp > 1e12) {
timestamp = RenderLoop.now();
}
// Calculate statistics variables
RenderLoop.deltaTime = RenderLoop.lastProcessTime ? (timestamp - RenderLoop.lastProcessTime) : 0;
RenderLoop.lastProcessTime = timestamp;
if (RenderLoop.deltaTime > 0) {
RenderLoop.fps = (0.9 * RenderLoop.fps) + 0.1 * (1000/RenderLoop.deltaTime);
}
// Call queue task
task = RenderLoop.queue.shift();
if (task) task.callback.apply(task.context, task.args.concat([timestamp]) );
// Call persistent tasks
var deltaTime, task;
for (var i = 0, len = RenderLoop.persistentTasks.length; i < len; i ++) {
task = RenderLoop.persistentTasks[i];
if (task) {
// Calculate difference in time between last call of this function, and current call. 0 if never called
deltaTime = task.lastTime ? (timestamp - task.lastTime) : 0;
task.lastTime = timestamp;
// Call task callback
task.callback.apply(task.context, task.args.concat([timestamp, deltaTime]) );
}
}
// Request next frame
RenderLoop.timer = RenderLoop.nextFrame( RenderLoop.process );
};
return RenderLoop;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment