UNPKG

animation-frame

Version:

An even better requestAnimationFrame

133 lines (114 loc) 3.7 kB
'use strict' var nativeImpl = require('./native') var now = require('./now') var performance = require('./performance') var root = require('./root') // Weird native implementation doesn't work if context is defined. var nativeRequest = nativeImpl.request var nativeCancel = nativeImpl.cancel /** * Animation frame constructor. * * Options: * - `useNative` use the native animation frame if possible, defaults to true * - `frameRate` pass a custom frame rate * * @param {Object|Number} options */ function AnimationFrame(options) { if (!(this instanceof AnimationFrame)) return new AnimationFrame(options) options || (options = {}) // Its a frame rate. if (typeof options == 'number') options = {frameRate: options} options.useNative != null || (options.useNative = true) this.options = options this.frameRate = options.frameRate || AnimationFrame.FRAME_RATE this._frameLength = 1000 / this.frameRate this._isCustomFrameRate = this.frameRate !== AnimationFrame.FRAME_RATE this._timeoutId = null this._callbacks = {} this._lastTickTime = 0 this._tickCounter = 0 } module.exports = AnimationFrame /** * Default frame rate used for shim implementation. Native implementation * will use the screen frame rate, but js have no way to detect it. * * If you know your target device, define it manually. * * @type {Number} * @api public */ AnimationFrame.FRAME_RATE = 60 /** * Replace the globally defined implementation or define it globally. * * @param {Object|Number} [options] * @api public */ AnimationFrame.shim = function(options) { var animationFrame = new AnimationFrame(options) root.requestAnimationFrame = function(callback) { return animationFrame.request(callback) } root.cancelAnimationFrame = function(id) { return animationFrame.cancel(id) } return animationFrame } /** * Request animation frame. * We will use the native RAF as soon as we know it does works. * * @param {Function} callback * @return {Number} timeout id or requested animation frame id * @api public */ AnimationFrame.prototype.request = function(callback) { var self = this // Alawys inc counter to ensure it never has a conflict with the native counter. // After the feature test phase we don't know exactly which implementation has been used. // Therefore on #cancel we do it for both. ++this._tickCounter if (nativeImpl.supported && this.options.useNative && !this._isCustomFrameRate) { return nativeRequest(callback) } if (!callback) throw new TypeError('Not enough arguments') if (this._timeoutId == null) { // Much faster than Math.max // http://jsperf.com/math-max-vs-comparison/3 // http://jsperf.com/date-now-vs-date-gettime/11 var delay = this._frameLength + this._lastTickTime - now() if (delay < 0) delay = 0 this._timeoutId = setTimeout(function() { self._lastTickTime = now() self._timeoutId = null ++self._tickCounter var callbacks = self._callbacks self._callbacks = {} for (var id in callbacks) { if (callbacks[id]) { if (nativeImpl.supported && self.options.useNative) { nativeRequest(callbacks[id]) } else { callbacks[id](performance.now()) } } } }, delay) } this._callbacks[this._tickCounter] = callback return this._tickCounter } /** * Cancel animation frame. * * @param {Number} timeout id or requested animation frame id * * @api public */ AnimationFrame.prototype.cancel = function(id) { if (nativeImpl.supported && this.options.useNative) nativeCancel(id) delete this._callbacks[id] }