Path: blob/trunk/third_party/closure/goog/async/animationdelay.js
2868 views
// Copyright 2012 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A delayed callback that pegs to the next animation frame16* instead of a user-configurable timeout.17*18* @author [email protected] (Nick Santos)19*/2021goog.provide('goog.async.AnimationDelay');2223goog.require('goog.Disposable');24goog.require('goog.events');25goog.require('goog.functions');26272829// TODO(nicksantos): Should we factor out the common code between this and30// goog.async.Delay? I'm not sure if there's enough code for this to really31// make sense. Subclassing seems like the wrong approach for a variety of32// reasons. Maybe there should be a common interface?33343536/**37* A delayed callback that pegs to the next animation frame38* instead of a user configurable timeout. By design, this should have39* the same interface as goog.async.Delay.40*41* Uses requestAnimationFrame and friends when available, but falls42* back to a timeout of goog.async.AnimationDelay.TIMEOUT.43*44* For more on requestAnimationFrame and how you can use it to create smoother45* animations, see:46* @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/47*48* @param {function(this:THIS, number)} listener Function to call49* when the delay completes. Will be passed the timestamp when it's called,50* in unix ms.51* @param {Window=} opt_window The window object to execute the delay in.52* Defaults to the global object.53* @param {THIS=} opt_handler The object scope to invoke the function in.54* @template THIS55* @constructor56* @struct57* @extends {goog.Disposable}58* @final59*/60goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {61goog.async.AnimationDelay.base(this, 'constructor');6263/**64* Identifier of the active delay timeout, or event listener,65* or null when inactive.66* @private {goog.events.Key|number}67*/68this.id_ = null;6970/**71* If we're using dom listeners.72* @private {?boolean}73*/74this.usingListeners_ = false;7576/**77* The function that will be invoked after a delay.78* @const79* @private80*/81this.listener_ = listener;8283/**84* The object context to invoke the callback in.85* @const86* @private {(THIS|undefined)}87*/88this.handler_ = opt_handler;8990/**91* @private {Window}92*/93this.win_ = opt_window || window;9495/**96* Cached callback function invoked when the delay finishes.97* @private {function()}98*/99this.callback_ = goog.bind(this.doAction_, this);100};101goog.inherits(goog.async.AnimationDelay, goog.Disposable);102103104/**105* Default wait timeout for animations (in milliseconds). Only used for timed106* animation, which uses a timer (setTimeout) to schedule animation.107*108* @type {number}109* @const110*/111goog.async.AnimationDelay.TIMEOUT = 20;112113114/**115* Name of event received from the requestAnimationFrame in Firefox.116*117* @type {string}118* @const119* @private120*/121goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint';122123124/**125* Starts the delay timer. The provided listener function will be called126* before the next animation frame.127*/128goog.async.AnimationDelay.prototype.start = function() {129this.stop();130this.usingListeners_ = false;131132var raf = this.getRaf_();133var cancelRaf = this.getCancelRaf_();134if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {135// Because Firefox (Gecko) runs animation in separate threads, it also saves136// time by running the requestAnimationFrame callbacks in that same thread.137// Sadly this breaks the assumption of implicit thread-safety in JS, and can138// thus create thread-based inconsistencies on counters etc.139//140// Calling cycleAnimations_ using the MozBeforePaint event instead of as141// callback fixes this.142//143// Trigger this condition only if the mozRequestAnimationFrame is available,144// but not the W3C requestAnimationFrame function (as in draft) or the145// equivalent cancel functions.146this.id_ = goog.events.listen(147this.win_, goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_,148this.callback_);149this.win_.mozRequestAnimationFrame(null);150this.usingListeners_ = true;151} else if (raf && cancelRaf) {152this.id_ = raf.call(this.win_, this.callback_);153} else {154this.id_ = this.win_.setTimeout(155// Prior to Firefox 13, Gecko passed a non-standard parameter156// to the callback that we want to ignore.157goog.functions.lock(this.callback_), goog.async.AnimationDelay.TIMEOUT);158}159};160161162/**163* Starts the delay timer if it's not already active.164*/165goog.async.AnimationDelay.prototype.startIfNotActive = function() {166if (!this.isActive()) {167this.start();168}169};170171172/**173* Stops the delay timer if it is active. No action is taken if the timer is not174* in use.175*/176goog.async.AnimationDelay.prototype.stop = function() {177if (this.isActive()) {178var raf = this.getRaf_();179var cancelRaf = this.getCancelRaf_();180if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {181goog.events.unlistenByKey(this.id_);182} else if (raf && cancelRaf) {183cancelRaf.call(this.win_, /** @type {number} */ (this.id_));184} else {185this.win_.clearTimeout(/** @type {number} */ (this.id_));186}187}188this.id_ = null;189};190191192/**193* Fires delay's action even if timer has already gone off or has not been194* started yet; guarantees action firing. Stops the delay timer.195*/196goog.async.AnimationDelay.prototype.fire = function() {197this.stop();198this.doAction_();199};200201202/**203* Fires delay's action only if timer is currently active. Stops the delay204* timer.205*/206goog.async.AnimationDelay.prototype.fireIfActive = function() {207if (this.isActive()) {208this.fire();209}210};211212213/**214* @return {boolean} True if the delay is currently active, false otherwise.215*/216goog.async.AnimationDelay.prototype.isActive = function() {217return this.id_ != null;218};219220221/**222* Invokes the callback function after the delay successfully completes.223* @private224*/225goog.async.AnimationDelay.prototype.doAction_ = function() {226if (this.usingListeners_ && this.id_) {227goog.events.unlistenByKey(this.id_);228}229this.id_ = null;230231// We are not using the timestamp returned by requestAnimationFrame232// because it may be either a Date.now-style time or a233// high-resolution time (depending on browser implementation). Using234// goog.now() will ensure that the timestamp used is consistent and235// compatible with goog.fx.Animation.236this.listener_.call(this.handler_, goog.now());237};238239240/** @override */241goog.async.AnimationDelay.prototype.disposeInternal = function() {242this.stop();243goog.async.AnimationDelay.base(this, 'disposeInternal');244};245246247/**248* @return {?function(function(number)): number} The requestAnimationFrame249* function, or null if not available on this browser.250* @private251*/252goog.async.AnimationDelay.prototype.getRaf_ = function() {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() {266var win = this.win_;267return win.cancelAnimationFrame || win.cancelRequestAnimationFrame ||268win.webkitCancelRequestAnimationFrame ||269win.mozCancelRequestAnimationFrame || win.oCancelRequestAnimationFrame ||270win.msCancelRequestAnimationFrame || null;271};272273274