Skip to content
Permalink
Fetching contributors…
Cannot retrieve contributors at this time
310 lines (282 sloc) 8.52 KB
/**
* 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;
You can’t perform that action at this time.