Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| /** | |
| * The MIT License (MIT) | |
| * | |
| * Copyright (c) 2015 Famous Industries Inc. | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| * of this software and associated documentation files (the "Software"), to deal | |
| * in the Software without restriction, including without limitation the rights | |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| * copies of the Software, and to permit persons to whom the Software is | |
| * furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| * THE SOFTWARE. | |
| */ | |
| 'use strict'; | |
| var polyfills = require('../polyfills'); | |
| var rAF = polyfills.requestAnimationFrame; | |
| var cAF = polyfills.cancelAnimationFrame; | |
| /** | |
| * Boolean constant indicating whether the RequestAnimationFrameLoop has access | |
| * to the document. The document is being used in order to subscribe for | |
| * visibilitychange events used for normalizing the RequestAnimationFrameLoop | |
| * time when e.g. when switching tabs. | |
| * | |
| * @constant | |
| * @type {Boolean} | |
| */ | |
| var DOCUMENT_ACCESS = typeof document !== 'undefined'; | |
| if (DOCUMENT_ACCESS) { | |
| var VENDOR_HIDDEN, VENDOR_VISIBILITY_CHANGE; | |
| // Opera 12.10 and Firefox 18 and later support | |
| if (typeof document.hidden !== 'undefined') { | |
| VENDOR_HIDDEN = 'hidden'; | |
| VENDOR_VISIBILITY_CHANGE = 'visibilitychange'; | |
| } | |
| else if (typeof document.mozHidden !== 'undefined') { | |
| VENDOR_HIDDEN = 'mozHidden'; | |
| VENDOR_VISIBILITY_CHANGE = 'mozvisibilitychange'; | |
| } | |
| else if (typeof document.msHidden !== 'undefined') { | |
| VENDOR_HIDDEN = 'msHidden'; | |
| VENDOR_VISIBILITY_CHANGE = 'msvisibilitychange'; | |
| } | |
| else if (typeof document.webkitHidden !== 'undefined') { | |
| VENDOR_HIDDEN = 'webkitHidden'; | |
| VENDOR_VISIBILITY_CHANGE = 'webkitvisibilitychange'; | |
| } | |
| } | |
| /** | |
| * RequestAnimationFrameLoop class used for updating objects on a frame-by-frame. | |
| * Synchronizes the `update` method invocations to the refresh rate of the | |
| * screen. Manages the `requestAnimationFrame`-loop by normalizing the passed in | |
| * timestamp when switching tabs. | |
| * | |
| * @class RequestAnimationFrameLoop | |
| */ | |
| function RequestAnimationFrameLoop() { | |
| var _this = this; | |
| // References to objects to be updated on next frame. | |
| this._updates = []; | |
| this._looper = function(time) { | |
| _this.loop(time); | |
| }; | |
| this._time = 0; | |
| this._stoppedAt = 0; | |
| this._sleep = 0; | |
| // Indicates whether the engine should be restarted when the tab/ window is | |
| // being focused again (visibility change). | |
| this._startOnVisibilityChange = true; | |
| // requestId as returned by requestAnimationFrame function; | |
| this._rAF = null; | |
| this._sleepDiff = true; | |
| // The engine is being started on instantiation. | |
| // TODO(alexanderGugel) | |
| this.start(); | |
| // The RequestAnimationFrameLoop supports running in a non-browser | |
| // environment (e.g. Worker). | |
| if (DOCUMENT_ACCESS) { | |
| document.addEventListener(VENDOR_VISIBILITY_CHANGE, function() { | |
| _this._onVisibilityChange(); | |
| }); | |
| } | |
| } | |
| /** | |
| * Handle the switching of tabs. | |
| * | |
| * @method | |
| * @private | |
| * | |
| * @return {undefined} undefined | |
| */ | |
| RequestAnimationFrameLoop.prototype._onVisibilityChange = function _onVisibilityChange() { | |
| if (document[VENDOR_HIDDEN]) { | |
| this._onUnfocus(); | |
| } | |
| else { | |
| this._onFocus(); | |
| } | |
| }; | |
| /** | |
| * Internal helper function to be invoked as soon as the window/ tab is being | |
| * focused after a visibiltiy change. | |
| * | |
| * @method | |
| * @private | |
| * | |
| * @return {undefined} undefined | |
| */ | |
| RequestAnimationFrameLoop.prototype._onFocus = function _onFocus() { | |
| if (this._startOnVisibilityChange) { | |
| this._start(); | |
| } | |
| }; | |
| /** | |
| * Internal helper function to be invoked as soon as the window/ tab is being | |
| * unfocused (hidden) after a visibiltiy change. | |
| * | |
| * @method _onFocus | |
| * @private | |
| * | |
| * @return {undefined} undefined | |
| */ | |
| RequestAnimationFrameLoop.prototype._onUnfocus = function _onUnfocus() { | |
| this._stop(); | |
| }; | |
| /** | |
| * Starts the RequestAnimationFrameLoop. When switching to a differnt tab/ | |
| * window (changing the visibiltiy), the engine will be retarted when switching | |
| * back to a visible state. | |
| * | |
| * @method | |
| * | |
| * @return {RequestAnimationFrameLoop} this | |
| */ | |
| RequestAnimationFrameLoop.prototype.start = function start() { | |
| if (!this._running) { | |
| this._startOnVisibilityChange = true; | |
| this._start(); | |
| } | |
| return this; | |
| }; | |
| /** | |
| * Internal version of RequestAnimationFrameLoop's start function, not affecting | |
| * behavior on visibilty change. | |
| * | |
| * @method | |
| * @private | |
| * | |
| * @return {undefined} undefined | |
| */ | |
| RequestAnimationFrameLoop.prototype._start = function _start() { | |
| this._running = true; | |
| this._sleepDiff = true; | |
| this._rAF = rAF(this._looper); | |
| }; | |
| /** | |
| * Stops the RequestAnimationFrameLoop. | |
| * | |
| * @method | |
| * @private | |
| * | |
| * @return {RequestAnimationFrameLoop} this | |
| */ | |
| RequestAnimationFrameLoop.prototype.stop = function stop() { | |
| if (this._running) { | |
| this._startOnVisibilityChange = false; | |
| this._stop(); | |
| } | |
| return this; | |
| }; | |
| /** | |
| * Internal version of RequestAnimationFrameLoop's stop function, not affecting | |
| * behavior on visibilty change. | |
| * | |
| * @method | |
| * @private | |
| * | |
| * @return {undefined} undefined | |
| */ | |
| RequestAnimationFrameLoop.prototype._stop = function _stop() { | |
| this._running = false; | |
| this._stoppedAt = this._time; | |
| // Bug in old versions of Fx. Explicitly cancel. | |
| cAF(this._rAF); | |
| }; | |
| /** | |
| * Determines whether the RequestAnimationFrameLoop is currently running or not. | |
| * | |
| * @method | |
| * | |
| * @return {Boolean} boolean value indicating whether the | |
| * RequestAnimationFrameLoop is currently running or not | |
| */ | |
| RequestAnimationFrameLoop.prototype.isRunning = function isRunning() { | |
| return this._running; | |
| }; | |
| /** | |
| * Updates all registered objects. | |
| * | |
| * @method | |
| * | |
| * @param {Number} time high resolution timstamp used for invoking the `update` | |
| * method on all registered objects | |
| * | |
| * @return {RequestAnimationFrameLoop} this | |
| */ | |
| RequestAnimationFrameLoop.prototype.step = function step (time) { | |
| this._time = time; | |
| if (this._sleepDiff) { | |
| this._sleep += time - this._stoppedAt; | |
| this._sleepDiff = false; | |
| } | |
| // The same timetamp will be emitted immediately before and after visibility | |
| // change. | |
| var normalizedTime = time - this._sleep; | |
| for (var i = 0, len = this._updates.length ; i < len ; i++) { | |
| this._updates[i].update(normalizedTime); | |
| } | |
| return this; | |
| }; | |
| /** | |
| * Method being called by `requestAnimationFrame` on every paint. Indirectly | |
| * recursive by scheduling a future invocation of itself on the next paint. | |
| * | |
| * @method | |
| * | |
| * @param {Number} time high resolution timstamp used for invoking the `update` | |
| * method on all registered objects | |
| * @return {RequestAnimationFrameLoop} this | |
| */ | |
| RequestAnimationFrameLoop.prototype.loop = function loop(time) { | |
| this.step(time); | |
| this._rAF = rAF(this._looper); | |
| return this; | |
| }; | |
| /** | |
| * Registeres an updateable object which `update` method should be invoked on | |
| * every paint, starting on the next paint (assuming the | |
| * RequestAnimationFrameLoop is running). | |
| * | |
| * @method | |
| * | |
| * @param {Object} updateable object to be updated | |
| * @param {Function} updateable.update update function to be called on the | |
| * registered object | |
| * | |
| * @return {RequestAnimationFrameLoop} this | |
| */ | |
| RequestAnimationFrameLoop.prototype.update = function update(updateable) { | |
| if (this._updates.indexOf(updateable) === -1) { | |
| this._updates.push(updateable); | |
| } | |
| return this; | |
| }; | |
| /** | |
| * Deregisters an updateable object previously registered using `update` to be | |
| * no longer updated. | |
| * | |
| * @method | |
| * | |
| * @param {Object} updateable updateable object previously registered using | |
| * `update` | |
| * | |
| * @return {RequestAnimationFrameLoop} this | |
| */ | |
| RequestAnimationFrameLoop.prototype.noLongerUpdate = function noLongerUpdate(updateable) { | |
| var index = this._updates.indexOf(updateable); | |
| if (index > -1) { | |
| this._updates.splice(index, 1); | |
| } | |
| return this; | |
| }; | |
| module.exports = RequestAnimationFrameLoop; |