Path: blob/trunk/third_party/closure/goog/async/animationdelay.js
3983 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview A delayed callback that pegs to the next animation frame8* instead of a user-configurable timeout.9*/1011goog.provide('goog.async.AnimationDelay');1213goog.require('goog.Disposable');14goog.require('goog.events');15goog.require('goog.functions');16171819// TODO(nicksantos): Should we factor out the common code between this and20// goog.async.Delay? I'm not sure if there's enough code for this to really21// make sense. Subclassing seems like the wrong approach for a variety of22// reasons. Maybe there should be a common interface?23242526/**27* A delayed callback that pegs to the next animation frame28* instead of a user configurable timeout. By design, this should have29* the same interface as goog.async.Delay.30*31* Uses requestAnimationFrame and friends when available, but falls32* back to a timeout of goog.async.AnimationDelay.TIMEOUT.33*34* For more on requestAnimationFrame and how you can use it to create smoother35* animations, see:36* @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/37*38* @param {function(this: THIS, number): void} listener Function to call39* when the delay completes. Will be passed the timestamp when it's called,40* in unix ms.41* @param {Window=} opt_window The window object to execute the delay in.42* Defaults to the global object.43* @param {THIS=} opt_handler The object scope to invoke the function in.44* @template THIS45* @constructor46* @struct47* @extends {goog.Disposable}48* @final49*/50goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {51'use strict';52goog.async.AnimationDelay.base(this, 'constructor');5354/**55* Identifier of the active delay timeout, or event listener,56* or null when inactive.57* @private {?goog.events.Key|number}58*/59this.id_ = null;6061/**62* If we're using dom listeners.63* @private {?boolean}64*/65this.usingListeners_ = false;6667/**68* The function that will be invoked after a delay.69* @const70* @private {function(this: THIS, number): void}71*/72this.listener_ = listener;7374/**75* The object context to invoke the callback in.76* @const77* @private {(THIS|undefined)}78*/79this.handler_ = opt_handler;8081/**82* @private {Window}83*/84this.win_ = opt_window || window;8586/**87* Cached callback function invoked when the delay finishes.88* @private {function()}89*/90this.callback_ = goog.bind(this.doAction_, this);91};92goog.inherits(goog.async.AnimationDelay, goog.Disposable);939495/**96* Default wait timeout for animations (in milliseconds). Only used for timed97* animation, which uses a timer (setTimeout) to schedule animation.98*99* @type {number}100* @const101*/102goog.async.AnimationDelay.TIMEOUT = 20;103104105/**106* Name of event received from the requestAnimationFrame in Firefox.107*108* @type {string}109* @const110* @private111*/112goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint';113114115/**116* Starts the delay timer. The provided listener function will be called117* before the next animation frame.118*/119goog.async.AnimationDelay.prototype.start = function() {120'use strict';121this.stop();122this.usingListeners_ = false;123124var raf = this.getRaf_();125var cancelRaf = this.getCancelRaf_();126if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {127// Because Firefox (Gecko) runs animation in separate threads, it also saves128// time by running the requestAnimationFrame callbacks in that same thread.129// Sadly this breaks the assumption of implicit thread-safety in JS, and can130// thus create thread-based inconsistencies on counters etc.131//132// Calling cycleAnimations_ using the MozBeforePaint event instead of as133// callback fixes this.134//135// Trigger this condition only if the mozRequestAnimationFrame is available,136// but not the W3C requestAnimationFrame function (as in draft) or the137// equivalent cancel functions.138this.id_ = goog.events.listen(139this.win_, goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_,140this.callback_);141this.win_.mozRequestAnimationFrame(null);142this.usingListeners_ = true;143} else if (raf && cancelRaf) {144this.id_ = raf.call(this.win_, this.callback_);145} else {146this.id_ = this.win_.setTimeout(147// Prior to Firefox 13, Gecko passed a non-standard parameter148// to the callback that we want to ignore.149goog.functions.lock(this.callback_), goog.async.AnimationDelay.TIMEOUT);150}151};152153154/**155* Starts the delay timer if it's not already active.156*/157goog.async.AnimationDelay.prototype.startIfNotActive = function() {158'use strict';159if (!this.isActive()) {160this.start();161}162};163164165/**166* Stops the delay timer if it is active. No action is taken if the timer is not167* in use.168*/169goog.async.AnimationDelay.prototype.stop = function() {170'use strict';171if (this.isActive()) {172var raf = this.getRaf_();173var cancelRaf = this.getCancelRaf_();174if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {175goog.events.unlistenByKey(this.id_);176} else if (raf && cancelRaf) {177cancelRaf.call(this.win_, /** @type {number} */ (this.id_));178} else {179this.win_.clearTimeout(/** @type {number} */ (this.id_));180}181}182this.id_ = null;183};184185186/**187* Fires delay's action even if timer has already gone off or has not been188* started yet; guarantees action firing. Stops the delay timer.189*/190goog.async.AnimationDelay.prototype.fire = function() {191'use strict';192this.stop();193this.doAction_();194};195196197/**198* Fires delay's action only if timer is currently active. Stops the delay199* timer.200*/201goog.async.AnimationDelay.prototype.fireIfActive = function() {202'use strict';203if (this.isActive()) {204this.fire();205}206};207208209/**210* @return {boolean} True if the delay is currently active, false otherwise.211*/212goog.async.AnimationDelay.prototype.isActive = function() {213'use strict';214return this.id_ != null;215};216217218/**219* Invokes the callback function after the delay successfully completes.220* @private221*/222goog.async.AnimationDelay.prototype.doAction_ = function() {223'use strict';224if (this.usingListeners_ && this.id_) {225goog.events.unlistenByKey(this.id_);226}227this.id_ = null;228229// We are not using the timestamp returned by requestAnimationFrame230// because it may be either a Date.now-style time or a231// high-resolution time (depending on browser implementation). Using232// goog.now() will ensure that the timestamp used is consistent and233// compatible with goog.fx.Animation.234this.listener_.call(this.handler_, goog.now());235};236237238/** @override */239goog.async.AnimationDelay.prototype.disposeInternal = function() {240'use strict';241this.stop();242goog.async.AnimationDelay.base(this, 'disposeInternal');243};244245246/**247* @return {?function(function(number)): number} The requestAnimationFrame248* function, or null if not available on this browser.249* @private250*/251goog.async.AnimationDelay.prototype.getRaf_ = function() {252'use strict';253var win = this.win_;254return win.requestAnimationFrame || win.webkitRequestAnimationFrame ||255win.mozRequestAnimationFrame || win.oRequestAnimationFrame ||256win.msRequestAnimationFrame || null;257};258259260/**261* @return {?function(number): undefined} The cancelAnimationFrame function,262* or null if not available on this browser.263* @private264*/265goog.async.AnimationDelay.prototype.getCancelRaf_ = function() {266'use strict';267var win = this.win_;268return win.cancelAnimationFrame || win.cancelRequestAnimationFrame ||269win.webkitCancelRequestAnimationFrame ||270win.mozCancelRequestAnimationFrame || win.oCancelRequestAnimationFrame ||271win.msCancelRequestAnimationFrame || null;272};273274275