animation.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Classes for doing animations and visual effects.
  16. *
  17. * (Based loosly on my animation code for 13thparallel.org, with extra
  18. * inspiration from the DojoToolkit's modifications to my code)
  19. * @author arv@google.com (Erik Arvidsson)
  20. */
  21. goog.provide('goog.fx.Animation');
  22. goog.provide('goog.fx.Animation.EventType');
  23. goog.provide('goog.fx.Animation.State');
  24. goog.provide('goog.fx.AnimationEvent');
  25. goog.require('goog.array');
  26. goog.require('goog.asserts');
  27. goog.require('goog.events.Event');
  28. goog.require('goog.fx.Transition');
  29. goog.require('goog.fx.TransitionBase');
  30. goog.require('goog.fx.anim');
  31. goog.require('goog.fx.anim.Animated');
  32. /**
  33. * Constructor for an animation object.
  34. * @param {Array<number>} start Array for start coordinates.
  35. * @param {Array<number>} end Array for end coordinates.
  36. * @param {number} duration Length of animation in milliseconds.
  37. * @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.
  38. * @constructor
  39. * @struct
  40. * @implements {goog.fx.anim.Animated}
  41. * @implements {goog.fx.Transition}
  42. * @extends {goog.fx.TransitionBase}
  43. */
  44. goog.fx.Animation = function(start, end, duration, opt_acc) {
  45. goog.fx.Animation.base(this, 'constructor');
  46. if (!goog.isArray(start) || !goog.isArray(end)) {
  47. throw Error('Start and end parameters must be arrays');
  48. }
  49. if (start.length != end.length) {
  50. throw Error('Start and end points must be the same length');
  51. }
  52. /**
  53. * Start point.
  54. * @type {Array<number>}
  55. * @protected
  56. */
  57. this.startPoint = start;
  58. /**
  59. * End point.
  60. * @type {Array<number>}
  61. * @protected
  62. */
  63. this.endPoint = end;
  64. /**
  65. * Duration of animation in milliseconds.
  66. * @type {number}
  67. * @protected
  68. */
  69. this.duration = duration;
  70. /**
  71. * Acceleration function, which must return a number between 0 and 1 for
  72. * inputs between 0 and 1.
  73. * @type {Function|undefined}
  74. * @private
  75. */
  76. this.accel_ = opt_acc;
  77. /**
  78. * Current coordinate for animation.
  79. * @type {Array<number>}
  80. * @protected
  81. */
  82. this.coords = [];
  83. /**
  84. * Whether the animation should use "right" rather than "left" to position
  85. * elements in RTL. This is a temporary flag to allow clients to transition
  86. * to the new behavior at their convenience. At some point it will be the
  87. * default.
  88. * @type {boolean}
  89. * @private
  90. */
  91. this.useRightPositioningForRtl_ = false;
  92. /**
  93. * Current frame rate.
  94. * @private {number}
  95. */
  96. this.fps_ = 0;
  97. /**
  98. * Percent of the way through the animation.
  99. * @protected {number}
  100. */
  101. this.progress = 0;
  102. /**
  103. * Timestamp for when last frame was run.
  104. * @protected {?number}
  105. */
  106. this.lastFrame = null;
  107. };
  108. goog.inherits(goog.fx.Animation, goog.fx.TransitionBase);
  109. /**
  110. * @return {number} The duration of this animation in milliseconds.
  111. */
  112. goog.fx.Animation.prototype.getDuration = function() {
  113. return this.duration;
  114. };
  115. /**
  116. * Sets whether the animation should use "right" rather than "left" to position
  117. * elements. This is a temporary flag to allow clients to transition
  118. * to the new component at their convenience. At some point "right" will be
  119. * used for RTL elements by default.
  120. * @param {boolean} useRightPositioningForRtl True if "right" should be used for
  121. * positioning, false if "left" should be used for positioning.
  122. */
  123. goog.fx.Animation.prototype.enableRightPositioningForRtl = function(
  124. useRightPositioningForRtl) {
  125. this.useRightPositioningForRtl_ = useRightPositioningForRtl;
  126. };
  127. /**
  128. * Whether the animation should use "right" rather than "left" to position
  129. * elements. This is a temporary flag to allow clients to transition
  130. * to the new component at their convenience. At some point "right" will be
  131. * used for RTL elements by default.
  132. * @return {boolean} True if "right" should be used for positioning, false if
  133. * "left" should be used for positioning.
  134. */
  135. goog.fx.Animation.prototype.isRightPositioningForRtlEnabled = function() {
  136. return this.useRightPositioningForRtl_;
  137. };
  138. /**
  139. * Events fired by the animation.
  140. * @enum {string}
  141. */
  142. goog.fx.Animation.EventType = {
  143. /**
  144. * Dispatched when played for the first time OR when it is resumed.
  145. * @deprecated Use goog.fx.Transition.EventType.PLAY.
  146. */
  147. PLAY: goog.fx.Transition.EventType.PLAY,
  148. /**
  149. * Dispatched only when the animation starts from the beginning.
  150. * @deprecated Use goog.fx.Transition.EventType.BEGIN.
  151. */
  152. BEGIN: goog.fx.Transition.EventType.BEGIN,
  153. /**
  154. * Dispatched only when animation is restarted after a pause.
  155. * @deprecated Use goog.fx.Transition.EventType.RESUME.
  156. */
  157. RESUME: goog.fx.Transition.EventType.RESUME,
  158. /**
  159. * Dispatched when animation comes to the end of its duration OR stop
  160. * is called.
  161. * @deprecated Use goog.fx.Transition.EventType.END.
  162. */
  163. END: goog.fx.Transition.EventType.END,
  164. /**
  165. * Dispatched only when stop is called.
  166. * @deprecated Use goog.fx.Transition.EventType.STOP.
  167. */
  168. STOP: goog.fx.Transition.EventType.STOP,
  169. /**
  170. * Dispatched only when animation comes to its end naturally.
  171. * @deprecated Use goog.fx.Transition.EventType.FINISH.
  172. */
  173. FINISH: goog.fx.Transition.EventType.FINISH,
  174. /**
  175. * Dispatched when an animation is paused.
  176. * @deprecated Use goog.fx.Transition.EventType.PAUSE.
  177. */
  178. PAUSE: goog.fx.Transition.EventType.PAUSE,
  179. /**
  180. * Dispatched each frame of the animation. This is where the actual animator
  181. * will listen.
  182. */
  183. ANIMATE: 'animate',
  184. /**
  185. * Dispatched when the animation is destroyed.
  186. */
  187. DESTROY: 'destroy'
  188. };
  189. /**
  190. * @deprecated Use goog.fx.anim.TIMEOUT.
  191. */
  192. goog.fx.Animation.TIMEOUT = goog.fx.anim.TIMEOUT;
  193. /**
  194. * Enum for the possible states of an animation.
  195. * @deprecated Use goog.fx.Transition.State instead.
  196. * @enum {number}
  197. */
  198. goog.fx.Animation.State = goog.fx.TransitionBase.State;
  199. /**
  200. * @deprecated Use goog.fx.anim.setAnimationWindow.
  201. * @param {Window} animationWindow The window in which to animate elements.
  202. */
  203. goog.fx.Animation.setAnimationWindow = function(animationWindow) {
  204. goog.fx.anim.setAnimationWindow(animationWindow);
  205. };
  206. /**
  207. * Starts or resumes an animation.
  208. * @param {boolean=} opt_restart Whether to restart the
  209. * animation from the beginning if it has been paused.
  210. * @return {boolean} Whether animation was started.
  211. * @override
  212. */
  213. goog.fx.Animation.prototype.play = function(opt_restart) {
  214. if (opt_restart || this.isStopped()) {
  215. this.progress = 0;
  216. this.coords = this.startPoint;
  217. } else if (this.isPlaying()) {
  218. return false;
  219. }
  220. goog.fx.anim.unregisterAnimation(this);
  221. var now = /** @type {number} */ (goog.now());
  222. this.startTime = now;
  223. if (this.isPaused()) {
  224. this.startTime -= this.duration * this.progress;
  225. }
  226. this.endTime = this.startTime + this.duration;
  227. this.lastFrame = this.startTime;
  228. if (!this.progress) {
  229. this.onBegin();
  230. }
  231. this.onPlay();
  232. if (this.isPaused()) {
  233. this.onResume();
  234. }
  235. this.setStatePlaying();
  236. goog.fx.anim.registerAnimation(this);
  237. this.cycle(now);
  238. return true;
  239. };
  240. /**
  241. * Stops the animation.
  242. * @param {boolean=} opt_gotoEnd If true the animation will move to the
  243. * end coords.
  244. * @override
  245. */
  246. goog.fx.Animation.prototype.stop = function(opt_gotoEnd) {
  247. goog.fx.anim.unregisterAnimation(this);
  248. this.setStateStopped();
  249. if (opt_gotoEnd) {
  250. this.progress = 1;
  251. }
  252. this.updateCoords_(this.progress);
  253. this.onStop();
  254. this.onEnd();
  255. };
  256. /**
  257. * Pauses the animation (iff it's playing).
  258. * @override
  259. */
  260. goog.fx.Animation.prototype.pause = function() {
  261. if (this.isPlaying()) {
  262. goog.fx.anim.unregisterAnimation(this);
  263. this.setStatePaused();
  264. this.onPause();
  265. }
  266. };
  267. /**
  268. * @return {number} The current progress of the animation, the number
  269. * is between 0 and 1 inclusive.
  270. */
  271. goog.fx.Animation.prototype.getProgress = function() {
  272. return this.progress;
  273. };
  274. /**
  275. * Sets the progress of the animation.
  276. * @param {number} progress The new progress of the animation.
  277. */
  278. goog.fx.Animation.prototype.setProgress = function(progress) {
  279. this.progress = progress;
  280. if (this.isPlaying()) {
  281. var now = goog.now();
  282. // If the animation is already playing, we recompute startTime and endTime
  283. // such that the animation plays consistently, that is:
  284. // now = startTime + progress * duration.
  285. this.startTime = now - this.duration * this.progress;
  286. this.endTime = this.startTime + this.duration;
  287. }
  288. };
  289. /**
  290. * Disposes of the animation. Stops an animation, fires a 'destroy' event and
  291. * then removes all the event handlers to clean up memory.
  292. * @override
  293. * @protected
  294. */
  295. goog.fx.Animation.prototype.disposeInternal = function() {
  296. if (!this.isStopped()) {
  297. this.stop(false);
  298. }
  299. this.onDestroy();
  300. goog.fx.Animation.base(this, 'disposeInternal');
  301. };
  302. /**
  303. * Stops an animation, fires a 'destroy' event and then removes all the event
  304. * handlers to clean up memory.
  305. * @deprecated Use dispose() instead.
  306. */
  307. goog.fx.Animation.prototype.destroy = function() {
  308. this.dispose();
  309. };
  310. /** @override */
  311. goog.fx.Animation.prototype.onAnimationFrame = function(now) {
  312. this.cycle(now);
  313. };
  314. /**
  315. * Handles the actual iteration of the animation in a timeout
  316. * @param {number} now The current time.
  317. */
  318. goog.fx.Animation.prototype.cycle = function(now) {
  319. goog.asserts.assertNumber(this.startTime);
  320. goog.asserts.assertNumber(this.endTime);
  321. goog.asserts.assertNumber(this.lastFrame);
  322. // Happens in rare system clock reset.
  323. if (now < this.startTime) {
  324. this.endTime = now + this.endTime - this.startTime;
  325. this.startTime = now;
  326. }
  327. this.progress = (now - this.startTime) / (this.endTime - this.startTime);
  328. if (this.progress > 1) {
  329. this.progress = 1;
  330. }
  331. this.fps_ = 1000 / (now - this.lastFrame);
  332. this.lastFrame = now;
  333. this.updateCoords_(this.progress);
  334. // Animation has finished.
  335. if (this.progress == 1) {
  336. this.setStateStopped();
  337. goog.fx.anim.unregisterAnimation(this);
  338. this.onFinish();
  339. this.onEnd();
  340. // Animation is still under way.
  341. } else if (this.isPlaying()) {
  342. this.onAnimate();
  343. }
  344. };
  345. /**
  346. * Calculates current coordinates, based on the current state. Applies
  347. * the acceleration function if it exists.
  348. * @param {number} t Percentage of the way through the animation as a decimal.
  349. * @private
  350. */
  351. goog.fx.Animation.prototype.updateCoords_ = function(t) {
  352. if (goog.isFunction(this.accel_)) {
  353. t = this.accel_(t);
  354. }
  355. this.coords = new Array(this.startPoint.length);
  356. for (var i = 0; i < this.startPoint.length; i++) {
  357. this.coords[i] =
  358. (this.endPoint[i] - this.startPoint[i]) * t + this.startPoint[i];
  359. }
  360. };
  361. /**
  362. * Dispatches the ANIMATE event. Sub classes should override this instead
  363. * of listening to the event.
  364. * @protected
  365. */
  366. goog.fx.Animation.prototype.onAnimate = function() {
  367. this.dispatchAnimationEvent(goog.fx.Animation.EventType.ANIMATE);
  368. };
  369. /**
  370. * Dispatches the DESTROY event. Sub classes should override this instead
  371. * of listening to the event.
  372. * @protected
  373. */
  374. goog.fx.Animation.prototype.onDestroy = function() {
  375. this.dispatchAnimationEvent(goog.fx.Animation.EventType.DESTROY);
  376. };
  377. /** @override */
  378. goog.fx.Animation.prototype.dispatchAnimationEvent = function(type) {
  379. this.dispatchEvent(new goog.fx.AnimationEvent(type, this));
  380. };
  381. /**
  382. * Class for an animation event object.
  383. * @param {string} type Event type.
  384. * @param {goog.fx.Animation} anim An animation object.
  385. * @constructor
  386. * @struct
  387. * @extends {goog.events.Event}
  388. */
  389. goog.fx.AnimationEvent = function(type, anim) {
  390. goog.fx.AnimationEvent.base(this, 'constructor', type);
  391. /**
  392. * The current coordinates.
  393. * @type {Array<number>}
  394. */
  395. this.coords = anim.coords;
  396. /**
  397. * The x coordinate.
  398. * @type {number}
  399. */
  400. this.x = anim.coords[0];
  401. /**
  402. * The y coordinate.
  403. * @type {number}
  404. */
  405. this.y = anim.coords[1];
  406. /**
  407. * The z coordinate.
  408. * @type {number}
  409. */
  410. this.z = anim.coords[2];
  411. /**
  412. * The current duration.
  413. * @type {number}
  414. */
  415. this.duration = anim.duration;
  416. /**
  417. * The current progress.
  418. * @type {number}
  419. */
  420. this.progress = anim.getProgress();
  421. /**
  422. * Frames per second so far.
  423. */
  424. this.fps = anim.fps_;
  425. /**
  426. * The state of the animation.
  427. * @type {number}
  428. */
  429. this.state = anim.getStateInternal();
  430. /**
  431. * The animation object.
  432. * @type {goog.fx.Animation}
  433. */
  434. // TODO(arv): This can be removed as this is the same as the target
  435. this.anim = anim;
  436. };
  437. goog.inherits(goog.fx.AnimationEvent, goog.events.Event);
  438. /**
  439. * Returns the coordinates as integers (rounded to nearest integer).
  440. * @return {!Array<number>} An array of the coordinates rounded to
  441. * the nearest integer.
  442. */
  443. goog.fx.AnimationEvent.prototype.coordsAsInts = function() {
  444. return goog.array.map(this.coords, Math.round);
  445. };