2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 YUI.add('anim-base', function(Y) {
11 * The Animation Utility provides an API for creating advanced transitions.
16 * Provides the base Anim class, for animating numeric properties.
19 * @submodule anim-base
23 * A class for constructing animation instances.
30 var RUNNING = 'running',
31 START_TIME = 'startTime',
32 ELAPSED_TIME = 'elapsedTime',
36 * @description fires when an animation begins.
37 * @param {Event} ev The start event.
44 * @description fires every frame of the animation.
45 * @param {Event} ev The tween event.
52 * @description fires after the animation completes.
53 * @param {Event} ev The end event.
59 REVERSE = 'reverse', // TODO: cleanup
60 ITERATION_COUNT = 'iterationCount',
69 Y.Anim.superclass.constructor.apply(this, arguments);
70 _instances[Y.stamp(this)] = this;
76 * Regex of properties that should use the default unit.
78 * @property RE_DEFAULT_UNIT
81 Y.Anim.RE_DEFAULT_UNIT = /^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i;
84 * The default unit to use with properties that pass the RE_DEFAULT_UNIT test.
86 * @property DEFAULT_UNIT
89 Y.Anim.DEFAULT_UNIT = 'px';
91 Y.Anim.DEFAULT_EASING = function (t, b, c, d) {
92 return c * t / d + b; // linear easing
96 * Bucket for custom getters and setters
103 get: function(anim, attr) {
104 return anim._getOffset(attr);
109 Y.Anim.behaviors.top = Y.Anim.behaviors.left;
112 * The default setter to use when setting object properties.
114 * @property DEFAULT_SETTER
117 Y.Anim.DEFAULT_SETTER = function(anim, att, from, to, elapsed, duration, fn, unit) {
119 anim._node.setStyle(att, fn(elapsed, NUM(from), NUM(to) - NUM(from), duration) + unit);
123 * The default getter to use when getting object properties.
125 * @property DEFAULT_GETTER
128 Y.Anim.DEFAULT_GETTER = function(anim, prop) {
129 return anim._node.getComputedStyle(prop);
134 * The object to be animated.
139 setter: function(node) {
149 * The length of the animation. Defaults to "1" (second).
150 * @attribute duration
158 * The method that will provide values to the attribute(s) during the animation.
159 * Defaults to "Easing.easeNone".
164 value: Y.Anim.DEFAULT_EASING,
166 setter: function(val) {
167 if (typeof val === 'string' && Y.Easing) {
168 return Y.Easing[val];
174 * The starting values for the animated properties.
175 * Fields may be strings, numbers, or functions.
176 * If a function is used, the return value becomes the from value.
177 * If no from value is specified, the DEFAULT_GETTER will be used.
184 * The ending values for the animated properties.
185 * Fields may be strings, numbers, or functions.
192 * Date stamp for the first frame of the animation.
193 * @attribute startTime
204 * Current time the animation has been running.
205 * @attribute elapsedTime
216 * Whether or not the animation is currently running.
224 return !!_running[Y.stamp(this)];
231 * The number of times the animation should run
232 * @attribute iterations
241 * The number of iterations that have occurred.
242 * Resets when an animation ends (reaches iteration count or stop() called).
243 * @attribute iterationCount
254 * How iterations of the animation should behave.
255 * Possible values are "normal" and "alternate".
256 * Normal will repeat the animation, alternate will reverse on every other pass.
258 * @attribute direction
263 value: 'normal' // | alternate (fwd on odd, rev on even per spec)
267 * Whether or not the animation is currently paused.
279 * If true, animation begins from last frame
292 * Runs all animation instances.
296 Y.Anim.run = function() {
297 for (var i in _instances) {
298 if (_instances[i].run) {
305 * Pauses all animation instances.
309 Y.Anim.pause = function() {
310 for (var i in _running) { // stop timer if nothing running
311 if (_running[i].pause) {
319 * Stops all animation instances.
323 Y.Anim.stop = function() {
324 for (var i in _running) { // stop timer if nothing running
325 if (_running[i].stop) {
332 Y.Anim._startTimer = function() {
334 _timer = setInterval(Y.Anim._runFrame, 1);
338 Y.Anim._stopTimer = function() {
339 clearInterval(_timer);
344 * Called per Interval to handle each animation frame.
349 Y.Anim._runFrame = function() {
351 for (var anim in _running) {
352 if (_running[anim]._runFrame) {
354 _running[anim]._runFrame();
363 Y.Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;
367 * Starts or resumes an animation.
368 * percent start time marker.
373 if (!this.get(RUNNING)) {
375 } else if (this.get(PAUSED)) {
382 * Pauses the animation and
383 * freezes it in its current state and time.
384 * Calling run() will continue where it left off.
389 if (this.get(RUNNING)) {
396 * Stops the animation and resets its time.
400 stop: function(finish) {
401 if (this.get(RUNNING) || this.get(PAUSED)) {
410 this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
411 this._actualFrames = 0;
412 if (!this.get(PAUSED)) {
413 this._initAnimAttr();
415 _running[Y.stamp(this)] = this;
416 Y.Anim._startTimer();
422 this._set(START_TIME, null);
423 this._set(PAUSED, true);
424 delete _running[Y.stamp(this)];
428 * @description fires when an animation is paused.
429 * @param {Event} ev The pause event.
435 _resume: function() {
436 this._set(PAUSED, false);
437 _running[Y.stamp(this)] = this;
441 * @description fires when an animation is resumed (run from pause).
442 * @param {Event} ev The pause event.
448 _end: function(finish) {
449 this._set(START_TIME, null);
450 this._set(ELAPSED_TIME, 0);
451 this._set(PAUSED, false);
453 delete _running[Y.stamp(this)];
454 this.fire(END, {elapsed: this.get(ELAPSED_TIME)});
457 _runFrame: function() {
458 var attr = this._runtimeAttr,
459 customAttr = Y.Anim.behaviors,
460 easing = attr.easing,
462 t = new Date() - this.get(START_TIME),
463 reversed = this.get(REVERSE),
475 for (var i in attr) {
478 setter = (i in customAttr && 'set' in customAttr[i]) ?
479 customAttr[i].set : Y.Anim.DEFAULT_SETTER;
482 setter(this, i, attribute.from, attribute.to, t, d, easing, attribute.unit);
483 } else { // ensure final frame value is set
484 // TODO: handle keyframes
485 setter(this, i, attribute.from, attribute.to, lastFrame, d, easing, attribute.unit);
490 this._actualFrames += 1;
491 this._set(ELAPSED_TIME, t);
499 _lastFrame: function() {
500 var iter = this.get('iterations'),
501 iterCount = this.get(ITERATION_COUNT);
504 if (iter === 'infinite' || iterCount < iter) {
505 if (this.get('direction') === 'alternate') {
506 this.set(REVERSE, !this.get(REVERSE)); // flip it
510 * @description fires when an animation begins an iteration.
511 * @param {Event} ev The iteration event.
514 this.fire('iteration');
520 this._set(START_TIME, new Date());
521 this._set(ITERATION_COUNT, iterCount);
524 _initAnimAttr: function() {
525 var from = this.get('from') || {},
526 to = this.get('to') || {},
527 dur = this.get('duration') * 1000,
528 node = this.get(NODE),
529 easing = this.get('easing') || {},
531 customAttr = Y.Anim.behaviors,
534 Y.each(to, function(val, name) {
535 if (typeof val === 'function') {
536 val = val.call(this, node);
540 if (begin === undefined) {
541 begin = (name in customAttr && 'get' in customAttr[name]) ?
542 customAttr[name].get(this, name) : Y.Anim.DEFAULT_GETTER(this, name);
543 } else if (typeof begin === 'function') {
544 begin = begin.call(this, node);
547 var mFrom = Y.Anim.RE_UNITS.exec(begin);
548 var mTo = Y.Anim.RE_UNITS.exec(val);
550 begin = mFrom ? mFrom[1] : begin;
551 end = mTo ? mTo[1] : val;
552 unit = mTo ? mTo[2] : mFrom ? mFrom[2] : ''; // one might be zero TODO: mixed units
554 if (!unit && Y.Anim.RE_DEFAULT_UNIT.test(name)) {
555 unit = Y.Anim.DEFAULT_UNIT;
558 if (!begin || !end) {
559 Y.error('invalid "from" or "to" for "' + name + '"', 'Anim');
570 attr.easing = easing;
574 this._runtimeAttr = attr;
578 // TODO: move to computedStyle? (browsers dont agree on default computed offsets)
579 _getOffset: function(attr) {
580 var node = this._node,
581 val = node.getComputedStyle(attr),
582 get = (attr === 'left') ? 'getX': 'getY',
583 set = (attr === 'left') ? 'setX': 'setY';
585 if (val === 'auto') {
586 var position = node.getStyle('position');
587 if (position === 'absolute' || position === 'fixed') {
599 Y.extend(Y.Anim, Y.Base, proto);
602 }, '3.0.0' ,{requires:['base-base', 'node-style']});