Created
October 1, 2013 01:37
-
-
Save nicoburns/6772816 to your computer and use it in GitHub Desktop.
renderloop: A simple requestAnimationFrame shim and wrapper for easy animating.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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