Path: blob/trunk/third_party/closure/goog/fx/animation.js
2868 views
// Copyright 2006 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 Classes for doing animations and visual effects.16*17* (Based loosly on my animation code for 13thparallel.org, with extra18* inspiration from the DojoToolkit's modifications to my code)19* @author [email protected] (Erik Arvidsson)20*/2122goog.provide('goog.fx.Animation');23goog.provide('goog.fx.Animation.EventType');24goog.provide('goog.fx.Animation.State');25goog.provide('goog.fx.AnimationEvent');2627goog.require('goog.array');28goog.require('goog.asserts');29goog.require('goog.events.Event');30goog.require('goog.fx.Transition');31goog.require('goog.fx.TransitionBase');32goog.require('goog.fx.anim');33goog.require('goog.fx.anim.Animated');34353637/**38* Constructor for an animation object.39* @param {Array<number>} start Array for start coordinates.40* @param {Array<number>} end Array for end coordinates.41* @param {number} duration Length of animation in milliseconds.42* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.43* @constructor44* @struct45* @implements {goog.fx.anim.Animated}46* @implements {goog.fx.Transition}47* @extends {goog.fx.TransitionBase}48*/49goog.fx.Animation = function(start, end, duration, opt_acc) {50goog.fx.Animation.base(this, 'constructor');5152if (!goog.isArray(start) || !goog.isArray(end)) {53throw Error('Start and end parameters must be arrays');54}5556if (start.length != end.length) {57throw Error('Start and end points must be the same length');58}5960/**61* Start point.62* @type {Array<number>}63* @protected64*/65this.startPoint = start;6667/**68* End point.69* @type {Array<number>}70* @protected71*/72this.endPoint = end;7374/**75* Duration of animation in milliseconds.76* @type {number}77* @protected78*/79this.duration = duration;8081/**82* Acceleration function, which must return a number between 0 and 1 for83* inputs between 0 and 1.84* @type {Function|undefined}85* @private86*/87this.accel_ = opt_acc;8889/**90* Current coordinate for animation.91* @type {Array<number>}92* @protected93*/94this.coords = [];9596/**97* Whether the animation should use "right" rather than "left" to position98* elements in RTL. This is a temporary flag to allow clients to transition99* to the new behavior at their convenience. At some point it will be the100* default.101* @type {boolean}102* @private103*/104this.useRightPositioningForRtl_ = false;105106/**107* Current frame rate.108* @private {number}109*/110this.fps_ = 0;111112/**113* Percent of the way through the animation.114* @protected {number}115*/116this.progress = 0;117118/**119* Timestamp for when last frame was run.120* @protected {?number}121*/122this.lastFrame = null;123};124goog.inherits(goog.fx.Animation, goog.fx.TransitionBase);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) {137this.useRightPositioningForRtl_ = useRightPositioningForRtl;138};139140141/**142* Whether the animation should use "right" rather than "left" to position143* elements. This is a temporary flag to allow clients to transition144* to the new component at their convenience. At some point "right" will be145* used for RTL elements by default.146* @return {boolean} True if "right" should be used for positioning, false if147* "left" should be used for positioning.148*/149goog.fx.Animation.prototype.isRightPositioningForRtlEnabled = function() {150return this.useRightPositioningForRtl_;151};152153154/**155* Events fired by the animation.156* @enum {string}157*/158goog.fx.Animation.EventType = {159/**160* Dispatched when played for the first time OR when it is resumed.161* @deprecated Use goog.fx.Transition.EventType.PLAY.162*/163PLAY: goog.fx.Transition.EventType.PLAY,164165/**166* Dispatched only when the animation starts from the beginning.167* @deprecated Use goog.fx.Transition.EventType.BEGIN.168*/169BEGIN: goog.fx.Transition.EventType.BEGIN,170171/**172* Dispatched only when animation is restarted after a pause.173* @deprecated Use goog.fx.Transition.EventType.RESUME.174*/175RESUME: goog.fx.Transition.EventType.RESUME,176177/**178* Dispatched when animation comes to the end of its duration OR stop179* is called.180* @deprecated Use goog.fx.Transition.EventType.END.181*/182END: goog.fx.Transition.EventType.END,183184/**185* Dispatched only when stop is called.186* @deprecated Use goog.fx.Transition.EventType.STOP.187*/188STOP: goog.fx.Transition.EventType.STOP,189190/**191* Dispatched only when animation comes to its end naturally.192* @deprecated Use goog.fx.Transition.EventType.FINISH.193*/194FINISH: goog.fx.Transition.EventType.FINISH,195196/**197* Dispatched when an animation is paused.198* @deprecated Use goog.fx.Transition.EventType.PAUSE.199*/200PAUSE: goog.fx.Transition.EventType.PAUSE,201202/**203* Dispatched each frame of the animation. This is where the actual animator204* will listen.205*/206ANIMATE: 'animate',207208/**209* Dispatched when the animation is destroyed.210*/211DESTROY: 'destroy'212};213214215/**216* @deprecated Use goog.fx.anim.TIMEOUT.217*/218goog.fx.Animation.TIMEOUT = goog.fx.anim.TIMEOUT;219220221/**222* Enum for the possible states of an animation.223* @deprecated Use goog.fx.Transition.State instead.224* @enum {number}225*/226goog.fx.Animation.State = goog.fx.TransitionBase.State;227228229/**230* @deprecated Use goog.fx.anim.setAnimationWindow.231* @param {Window} animationWindow The window in which to animate elements.232*/233goog.fx.Animation.setAnimationWindow = function(animationWindow) {234goog.fx.anim.setAnimationWindow(animationWindow);235};236237238/**239* Starts or resumes an animation.240* @param {boolean=} opt_restart Whether to restart the241* animation from the beginning if it has been paused.242* @return {boolean} Whether animation was started.243* @override244*/245goog.fx.Animation.prototype.play = function(opt_restart) {246if (opt_restart || this.isStopped()) {247this.progress = 0;248this.coords = this.startPoint;249} else if (this.isPlaying()) {250return false;251}252253goog.fx.anim.unregisterAnimation(this);254255var now = /** @type {number} */ (goog.now());256257this.startTime = now;258if (this.isPaused()) {259this.startTime -= this.duration * this.progress;260}261262this.endTime = this.startTime + this.duration;263this.lastFrame = this.startTime;264265if (!this.progress) {266this.onBegin();267}268269this.onPlay();270271if (this.isPaused()) {272this.onResume();273}274275this.setStatePlaying();276277goog.fx.anim.registerAnimation(this);278this.cycle(now);279280return true;281};282283284/**285* Stops the animation.286* @param {boolean=} opt_gotoEnd If true the animation will move to the287* end coords.288* @override289*/290goog.fx.Animation.prototype.stop = function(opt_gotoEnd) {291goog.fx.anim.unregisterAnimation(this);292this.setStateStopped();293294if (opt_gotoEnd) {295this.progress = 1;296}297298this.updateCoords_(this.progress);299300this.onStop();301this.onEnd();302};303304305/**306* Pauses the animation (iff it's playing).307* @override308*/309goog.fx.Animation.prototype.pause = function() {310if (this.isPlaying()) {311goog.fx.anim.unregisterAnimation(this);312this.setStatePaused();313this.onPause();314}315};316317318/**319* @return {number} The current progress of the animation, the number320* is between 0 and 1 inclusive.321*/322goog.fx.Animation.prototype.getProgress = function() {323return this.progress;324};325326327/**328* Sets the progress of the animation.329* @param {number} progress The new progress of the animation.330*/331goog.fx.Animation.prototype.setProgress = function(progress) {332this.progress = progress;333if (this.isPlaying()) {334var now = goog.now();335// If the animation is already playing, we recompute startTime and endTime336// such that the animation plays consistently, that is:337// now = startTime + progress * duration.338this.startTime = now - this.duration * this.progress;339this.endTime = this.startTime + this.duration;340}341};342343344/**345* Disposes of the animation. Stops an animation, fires a 'destroy' event and346* then removes all the event handlers to clean up memory.347* @override348* @protected349*/350goog.fx.Animation.prototype.disposeInternal = function() {351if (!this.isStopped()) {352this.stop(false);353}354this.onDestroy();355goog.fx.Animation.base(this, 'disposeInternal');356};357358359/**360* Stops an animation, fires a 'destroy' event and then removes all the event361* handlers to clean up memory.362* @deprecated Use dispose() instead.363*/364goog.fx.Animation.prototype.destroy = function() {365this.dispose();366};367368369/** @override */370goog.fx.Animation.prototype.onAnimationFrame = function(now) {371this.cycle(now);372};373374375/**376* Handles the actual iteration of the animation in a timeout377* @param {number} now The current time.378*/379goog.fx.Animation.prototype.cycle = function(now) {380goog.asserts.assertNumber(this.startTime);381goog.asserts.assertNumber(this.endTime);382goog.asserts.assertNumber(this.lastFrame);383// Happens in rare system clock reset.384if (now < this.startTime) {385this.endTime = now + this.endTime - this.startTime;386this.startTime = now;387}388this.progress = (now - this.startTime) / (this.endTime - this.startTime);389390if (this.progress > 1) {391this.progress = 1;392}393394this.fps_ = 1000 / (now - this.lastFrame);395this.lastFrame = now;396397this.updateCoords_(this.progress);398399// Animation has finished.400if (this.progress == 1) {401this.setStateStopped();402goog.fx.anim.unregisterAnimation(this);403404this.onFinish();405this.onEnd();406407// Animation is still under way.408} else if (this.isPlaying()) {409this.onAnimate();410}411};412413414/**415* Calculates current coordinates, based on the current state. Applies416* the acceleration function if it exists.417* @param {number} t Percentage of the way through the animation as a decimal.418* @private419*/420goog.fx.Animation.prototype.updateCoords_ = function(t) {421if (goog.isFunction(this.accel_)) {422t = this.accel_(t);423}424this.coords = new Array(this.startPoint.length);425for (var i = 0; i < this.startPoint.length; i++) {426this.coords[i] =427(this.endPoint[i] - this.startPoint[i]) * t + this.startPoint[i];428}429};430431432/**433* Dispatches the ANIMATE event. Sub classes should override this instead434* of listening to the event.435* @protected436*/437goog.fx.Animation.prototype.onAnimate = function() {438this.dispatchAnimationEvent(goog.fx.Animation.EventType.ANIMATE);439};440441442/**443* Dispatches the DESTROY event. Sub classes should override this instead444* of listening to the event.445* @protected446*/447goog.fx.Animation.prototype.onDestroy = function() {448this.dispatchAnimationEvent(goog.fx.Animation.EventType.DESTROY);449};450451452/** @override */453goog.fx.Animation.prototype.dispatchAnimationEvent = function(type) {454this.dispatchEvent(new goog.fx.AnimationEvent(type, this));455};456457458459/**460* Class for an animation event object.461* @param {string} type Event type.462* @param {goog.fx.Animation} anim An animation object.463* @constructor464* @struct465* @extends {goog.events.Event}466*/467goog.fx.AnimationEvent = function(type, anim) {468goog.fx.AnimationEvent.base(this, 'constructor', type);469470/**471* The current coordinates.472* @type {Array<number>}473*/474this.coords = anim.coords;475476/**477* The x coordinate.478* @type {number}479*/480this.x = anim.coords[0];481482/**483* The y coordinate.484* @type {number}485*/486this.y = anim.coords[1];487488/**489* The z coordinate.490* @type {number}491*/492this.z = anim.coords[2];493494/**495* The current duration.496* @type {number}497*/498this.duration = anim.duration;499500/**501* The current progress.502* @type {number}503*/504this.progress = anim.getProgress();505506/**507* Frames per second so far.508*/509this.fps = anim.fps_;510511/**512* The state of the animation.513* @type {number}514*/515this.state = anim.getStateInternal();516517/**518* The animation object.519* @type {goog.fx.Animation}520*/521// TODO(arv): This can be removed as this is the same as the target522this.anim = anim;523};524goog.inherits(goog.fx.AnimationEvent, goog.events.Event);525526527/**528* Returns the coordinates as integers (rounded to nearest integer).529* @return {!Array<number>} An array of the coordinates rounded to530* the nearest integer.531*/532goog.fx.AnimationEvent.prototype.coordsAsInts = function() {533return goog.array.map(this.coords, Math.round);534};535536537