animation-frame
Version:
An even better requestAnimationFrame
133 lines (114 loc) • 3.7 kB
JavaScript
'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]
}