Path: blob/trunk/third_party/closure/goog/fx/animation.js
4146 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Classes for doing animations and visual effects.8*9* (Based loosly on my animation code for 13thparallel.org, with extra10* inspiration from the DojoToolkit's modifications to my code)11*/1213goog.provide('goog.fx.Animation');14goog.provide('goog.fx.Animation.EventType');15goog.provide('goog.fx.Animation.State');16goog.provide('goog.fx.AnimationEvent');1718goog.require('goog.asserts');19goog.require('goog.events.Event');20goog.require('goog.fx.Transition');21goog.require('goog.fx.TransitionBase');22goog.require('goog.fx.anim');23goog.require('goog.fx.anim.Animated');24252627/**28* Constructor for an animation object.29* @param {Array<number>} start Array for start coordinates.30* @param {Array<number>} end Array for end coordinates.31* @param {number} duration Length of animation in milliseconds.32* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.33* @constructor34* @struct35* @implements {goog.fx.anim.Animated}36* @implements {goog.fx.Transition}37* @extends {goog.fx.TransitionBase}38*/39goog.fx.Animation = function(start, end, duration, opt_acc) {40'use strict';41goog.fx.Animation.base(this, 'constructor');4243if (!Array.isArray(start) || !Array.isArray(end)) {44throw new Error('Start and end parameters must be arrays');45}4647if (start.length != end.length) {48throw new Error('Start and end points must be the same length');49}5051/**52* Start point.53* @type {Array<number>}54* @protected55*/56this.startPoint = start;5758/**59* End point.60* @type {Array<number>}61* @protected62*/63this.endPoint = end;6465/**66* Duration of animation in milliseconds.67* @type {number}68* @protected69*/70this.duration = duration;7172/**73* Acceleration function, which must return a number between 0 and 1 for74* inputs between 0 and 1.75* @type {Function|undefined}76* @private77*/78this.accel_ = opt_acc;7980/**81* Current coordinate for animation.82* @type {Array<number>}83* @protected84*/85this.coords = [];8687/**88* Whether the animation should use "right" rather than "left" to position89* elements in RTL. This is a temporary flag to allow clients to transition90* to the new behavior at their convenience. At some point it will be the91* default.92* @type {boolean}93* @private94*/95this.useRightPositioningForRtl_ = false;9697/**98* Current frame rate.99* @private {number}100*/101this.fps_ = 0;102103/**104* Percent of the way through the animation.105* @protected {number}106*/107this.progress = 0;108109/**110* Timestamp for when last frame was run.111* @protected {?number}112*/113this.lastFrame = null;114};115goog.inherits(goog.fx.Animation, goog.fx.TransitionBase);116117118/**119* @return {number} The duration of this animation in milliseconds.120*/121goog.fx.Animation.prototype.getDuration = function() {122'use strict';123return this.duration;124};125126127/**128* Sets whether the animation should use "right" rather than "left" to position129* elements. This is a temporary flag to allow clients to transition130* to the new component at their convenience. At some point "right" will be131* used for RTL elements by default.132* @param {boolean} useRightPositioningForRtl True if "right" should be used for133* positioning, false if "left" should be used for positioning.134*/135goog.fx.Animation.prototype.enableRightPositioningForRtl = function(136useRightPositioningForRtl) {137'use strict';138this.useRightPositioningForRtl_ = useRightPositioningForRtl;139};140141142/**143* Whether the animation should use "right" rather than "left" to position144* elements. This is a temporary flag to allow clients to transition145* to the new component at their convenience. At some point "right" will be146* used for RTL elements by default.147* @return {boolean} True if "right" should be used for positioning, false if148* "left" should be used for positioning.149*/150goog.fx.Animation.prototype.isRightPositioningForRtlEnabled = function() {151'use strict';152return this.useRightPositioningForRtl_;153};154155156/**157* Events fired by the animation.158* @enum {string}159*/160goog.fx.Animation.EventType = {161/**162* Dispatched when played for the first time OR when it is resumed.163* @deprecated Use goog.fx.Transition.EventType.PLAY.164*/165PLAY: goog.fx.Transition.EventType.PLAY,166167/**168* Dispatched only when the animation starts from the beginning.169* @deprecated Use goog.fx.Transition.EventType.BEGIN.170*/171BEGIN: goog.fx.Transition.EventType.BEGIN,172173/**174* Dispatched only when animation is restarted after a pause.175* @deprecated Use goog.fx.Transition.EventType.RESUME.176*/177RESUME: goog.fx.Transition.EventType.RESUME,178179/**180* Dispatched when animation comes to the end of its duration OR stop181* is called.182* @deprecated Use goog.fx.Transition.EventType.END.183*/184END: goog.fx.Transition.EventType.END,185186/**187* Dispatched only when stop is called.188* @deprecated Use goog.fx.Transition.EventType.STOP.189*/190STOP: goog.fx.Transition.EventType.STOP,191192/**193* Dispatched only when animation comes to its end naturally.194* @deprecated Use goog.fx.Transition.EventType.FINISH.195*/196FINISH: goog.fx.Transition.EventType.FINISH,197198/**199* Dispatched when an animation is paused.200* @deprecated Use goog.fx.Transition.EventType.PAUSE.201*/202PAUSE: goog.fx.Transition.EventType.PAUSE,203204/**205* Dispatched each frame of the animation. This is where the actual animator206* will listen.207*/208ANIMATE: 'animate',209210/**211* Dispatched when the animation is destroyed.212*/213DESTROY: 'destroy'214};215216217/**218* @deprecated Use goog.fx.anim.TIMEOUT.219*/220goog.fx.Animation.TIMEOUT = goog.fx.anim.TIMEOUT;221222223/**224* Enum for the possible states of an animation.225* @deprecated Use goog.fx.Transition.State instead.226* @enum {number}227*/228goog.fx.Animation.State = goog.fx.TransitionBase.State;229230231/**232* @deprecated Use goog.fx.anim.setAnimationWindow.233* @param {Window} animationWindow The window in which to animate elements.234*/235goog.fx.Animation.setAnimationWindow = function(animationWindow) {236'use strict';237goog.fx.anim.setAnimationWindow(animationWindow);238};239240241/**242* Starts or resumes an animation.243* @param {boolean=} opt_restart Whether to restart the244* animation from the beginning if it has been paused.245* @return {boolean} Whether animation was started.246* @override247*/248goog.fx.Animation.prototype.play = function(opt_restart) {249'use strict';250if (opt_restart || this.isStopped()) {251this.progress = 0;252this.coords = this.startPoint;253} else if (this.isPlaying()) {254return false;255}256257goog.fx.anim.unregisterAnimation(this);258259var now = /** @type {number} */ (goog.now());260261this.startTime = now;262if (this.isPaused()) {263this.startTime -= this.duration * this.progress;264}265266this.endTime = this.startTime + this.duration;267this.lastFrame = this.startTime;268269if (!this.progress) {270this.onBegin();271}272273this.onPlay();274275if (this.isPaused()) {276this.onResume();277}278279this.setStatePlaying();280281goog.fx.anim.registerAnimation(this);282this.cycle(now);283284return true;285};286287288/**289* Stops the animation.290* @param {boolean=} opt_gotoEnd If true the animation will move to the291* end coords.292* @override293*/294goog.fx.Animation.prototype.stop = function(opt_gotoEnd) {295'use strict';296goog.fx.anim.unregisterAnimation(this);297this.setStateStopped();298299if (opt_gotoEnd) {300this.progress = 1;301}302303this.updateCoords_(this.progress);304305this.onStop();306this.onEnd();307};308309310/**311* Pauses the animation (iff it's playing).312* @override313*/314goog.fx.Animation.prototype.pause = function() {315'use strict';316if (this.isPlaying()) {317goog.fx.anim.unregisterAnimation(this);318this.setStatePaused();319this.onPause();320}321};322323324/**325* @return {number} The current progress of the animation, the number326* is between 0 and 1 inclusive.327*/328goog.fx.Animation.prototype.getProgress = function() {329'use strict';330return this.progress;331};332333334/**335* Sets the progress of the animation.336* @param {number} progress The new progress of the animation.337*/338goog.fx.Animation.prototype.setProgress = function(progress) {339'use strict';340this.progress = progress;341if (this.isPlaying()) {342var now = goog.now();343// If the animation is already playing, we recompute startTime and endTime344// such that the animation plays consistently, that is:345// now = startTime + progress * duration.346this.startTime = now - this.duration * this.progress;347this.endTime = this.startTime + this.duration;348}349};350351352/**353* Disposes of the animation. Stops an animation, fires a 'destroy' event and354* then removes all the event handlers to clean up memory.355* @override356* @protected357*/358goog.fx.Animation.prototype.disposeInternal = function() {359'use strict';360if (!this.isStopped()) {361this.stop(false);362}363this.onDestroy();364goog.fx.Animation.base(this, 'disposeInternal');365};366367368/**369* Stops an animation, fires a 'destroy' event and then removes all the event370* handlers to clean up memory.371* @deprecated Use dispose() instead.372*/373goog.fx.Animation.prototype.destroy = function() {374'use strict';375this.dispose();376};377378379/** @override */380goog.fx.Animation.prototype.onAnimationFrame = function(now) {381'use strict';382this.cycle(now);383};384385386/**387* Handles the actual iteration of the animation in a timeout388* @param {number} now The current time.389*/390goog.fx.Animation.prototype.cycle = function(now) {391'use strict';392goog.asserts.assertNumber(this.startTime);393goog.asserts.assertNumber(this.endTime);394goog.asserts.assertNumber(this.lastFrame);395// Happens in rare system clock reset.396if (now < this.startTime) {397this.endTime = now + this.endTime - this.startTime;398this.startTime = now;399}400this.progress = (now - this.startTime) / (this.endTime - this.startTime);401402if (this.progress > 1) {403this.progress = 1;404}405406this.fps_ = 1000 / (now - this.lastFrame);407this.lastFrame = now;408409this.updateCoords_(this.progress);410411// Animation has finished.412if (this.progress == 1) {413this.setStateStopped();414goog.fx.anim.unregisterAnimation(this);415416this.onFinish();417this.onEnd();418419// Animation is still under way.420} else if (this.isPlaying()) {421this.onAnimate();422}423};424425426/**427* Calculates current coordinates, based on the current state. Applies428* the acceleration function if it exists.429* @param {number} t Percentage of the way through the animation as a decimal.430* @private431*/432goog.fx.Animation.prototype.updateCoords_ = function(t) {433'use strict';434if (typeof this.accel_ === 'function') {435t = this.accel_(t);436}437this.coords = new Array(this.startPoint.length);438for (var i = 0; i < this.startPoint.length; i++) {439this.coords[i] =440(this.endPoint[i] - this.startPoint[i]) * t + this.startPoint[i];441}442};443444445/**446* Dispatches the ANIMATE event. Sub classes should override this instead447* of listening to the event.448* @protected449*/450goog.fx.Animation.prototype.onAnimate = function() {451'use strict';452this.dispatchAnimationEvent(goog.fx.Animation.EventType.ANIMATE);453};454455456/**457* Dispatches the DESTROY event. Sub classes should override this instead458* of listening to the event.459* @protected460*/461goog.fx.Animation.prototype.onDestroy = function() {462'use strict';463this.dispatchAnimationEvent(goog.fx.Animation.EventType.DESTROY);464};465466467/** @override */468goog.fx.Animation.prototype.dispatchAnimationEvent = function(type) {469'use strict';470this.dispatchEvent(new goog.fx.AnimationEvent(type, this));471};472473474475/**476* Class for an animation event object.477* @param {string} type Event type.478* @param {goog.fx.Animation} anim An animation object.479* @constructor480* @struct481* @extends {goog.events.Event}482*/483goog.fx.AnimationEvent = function(type, anim) {484'use strict';485goog.fx.AnimationEvent.base(this, 'constructor', type);486487/**488* The current coordinates.489* @type {Array<number>}490*/491this.coords = anim.coords;492493/**494* The x coordinate.495* @type {number}496*/497this.x = anim.coords[0];498499/**500* The y coordinate.501* @type {number}502*/503this.y = anim.coords[1];504505/**506* The z coordinate.507* @type {number}508*/509this.z = anim.coords[2];510511/**512* The current duration.513* @type {number}514*/515this.duration = anim.duration;516517/**518* The current progress.519* @type {number}520*/521this.progress = anim.getProgress();522523/**524* Frames per second so far.525*/526this.fps = anim.fps_;527528/**529* The state of the animation.530* @type {number}531*/532this.state = anim.getStateInternal();533534/**535* The animation object.536* @type {goog.fx.Animation}537*/538// TODO(arv): This can be removed as this is the same as the target539this.anim = anim;540};541goog.inherits(goog.fx.AnimationEvent, goog.events.Event);542543544/**545* Returns the coordinates as integers (rounded to nearest integer).546* @return {!Array<number>} An array of the coordinates rounded to547* the nearest integer.548*/549goog.fx.AnimationEvent.prototype.coordsAsInts = function() {550'use strict';551return this.coords.map(Math.round);552};553554555