3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('anim-base', function(Y) {
10 * The Animation Utility provides an API for creating advanced transitions.
15 * Provides the base Anim class, for animating numeric properties.
18 * @submodule anim-base
22 * A class for constructing animation instances.
29 var RUNNING = 'running',
30 START_TIME = 'startTime',
31 ELAPSED_TIME = 'elapsedTime',
35 * @description fires when an animation begins.
36 * @param {Event} ev The start event.
43 * @description fires every frame of the animation.
44 * @param {Event} ev The tween event.
51 * @description fires after the animation completes.
52 * @param {Event} ev The end event.
58 REVERSE = 'reverse', // TODO: cleanup
59 ITERATION_COUNT = 'iterationCount',
67 Y.Anim.superclass.constructor.apply(this, arguments);
68 Y.Anim._instances[Y.stamp(this)] = this;
73 Y.Anim._instances = {};
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 * Time in milliseconds passed to setInterval for frame processing
98 * @property intervalTime
102 Y.Anim._intervalTime = 20;
105 * Bucket for custom getters and setters
107 * @property behaviors
112 get: function(anim, attr) {
113 return anim._getOffset(attr);
118 Y.Anim.behaviors.top = Y.Anim.behaviors.left;
121 * The default setter to use when setting object properties.
123 * @property DEFAULT_SETTER
126 Y.Anim.DEFAULT_SETTER = function(anim, att, from, to, elapsed, duration, fn, unit) {
127 var node = anim._node,
128 domNode = node._node,
129 val = fn(elapsed, NUM(from), NUM(to) - NUM(from), duration);
130 //make sure node instance
131 if (domNode && (domNode.style || domNode.attributes)) {
132 if (att in domNode.style || att in Y.DOM.CUSTOM_STYLES) {
134 node.setStyle(att, val + unit);
135 } else if (domNode.attributes[att]) {
136 node.setAttribute(att, val);
138 } else if (node.set) {
144 * The default getter to use when getting object properties.
146 * @property DEFAULT_GETTER
149 Y.Anim.DEFAULT_GETTER = function(anim, att) {
150 var node = anim._node,
151 domNode = node._node,
153 //make sure node instance
154 if (domNode && (domNode.style || domNode.attributes)) {
155 if (att in domNode.style || att in Y.DOM.CUSTOM_STYLES) {
156 val = node.getComputedStyle(att);
157 } else if (domNode.attributes[att]) {
158 val = node.getAttribute(att);
160 } else if (node.get) {
169 * The object to be animated.
174 setter: function(node) {
176 if (typeof node == 'string' || node.nodeType) {
183 Y.log(node + ' is not a valid node', 'warn', 'Anim');
190 * The length of the animation. Defaults to "1" (second).
191 * @attribute duration
199 * The method that will provide values to the attribute(s) during the animation.
200 * Defaults to "Easing.easeNone".
205 value: Y.Anim.DEFAULT_EASING,
207 setter: function(val) {
208 if (typeof val === 'string' && Y.Easing) {
209 return Y.Easing[val];
215 * The starting values for the animated properties.
217 * Fields may be strings, numbers, or functions.
218 * If a function is used, the return value becomes the from value.
219 * If no from value is specified, the DEFAULT_GETTER will be used.
220 * Supports any unit, provided it matches the "to" (or default)
221 * unit (e.g. `{width: '10em', color: 'rgb(0, 0 0)', borderColor: '#ccc'}`).
223 * If using the default ('px' for length-based units), the unit may be omitted
224 * (e.g. `{width: 100}, borderColor: 'ccc'}`, which defaults to pixels
225 * and hex, respectively).
233 * The ending values for the animated properties.
235 * Fields may be strings, numbers, or functions.
236 * Supports any unit, provided it matches the "from" (or default)
237 * unit (e.g. `{width: '50%', color: 'red', borderColor: '#ccc'}`).
239 * If using the default ('px' for length-based units), the unit may be omitted
240 * (e.g. `{width: 100, borderColor: 'ccc'}`, which defaults to pixels
241 * and hex, respectively).
249 * Date stamp for the first frame of the animation.
250 * @attribute startTime
261 * Current time the animation has been running.
262 * @attribute elapsedTime
273 * Whether or not the animation is currently running.
281 return !!_running[Y.stamp(this)];
288 * The number of times the animation should run
289 * @attribute iterations
298 * The number of iterations that have occurred.
299 * Resets when an animation ends (reaches iteration count or stop() called).
300 * @attribute iterationCount
311 * How iterations of the animation should behave.
312 * Possible values are "normal" and "alternate".
313 * Normal will repeat the animation, alternate will reverse on every other pass.
315 * @attribute direction
320 value: 'normal' // | alternate (fwd on odd, rev on even per spec)
324 * Whether or not the animation is currently paused.
336 * If true, animation begins from last frame
349 * Runs all animation instances.
353 Y.Anim.run = function() {
354 var instances = Y.Anim._instances;
355 for (var i in instances) {
356 if (instances[i].run) {
363 * Pauses all animation instances.
367 Y.Anim.pause = function() {
368 for (var i in _running) { // stop timer if nothing running
369 if (_running[i].pause) {
378 * Stops all animation instances.
382 Y.Anim.stop = function() {
383 for (var i in _running) { // stop timer if nothing running
384 if (_running[i].stop) {
391 Y.Anim._startTimer = function() {
393 _timer = setInterval(Y.Anim._runFrame, Y.Anim._intervalTime);
397 Y.Anim._stopTimer = function() {
398 clearInterval(_timer);
403 * Called per Interval to handle each animation frame.
408 Y.Anim._runFrame = function() {
410 for (var anim in _running) {
411 if (_running[anim]._runFrame) {
413 _running[anim]._runFrame();
422 Y.Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;
426 * Starts or resumes an animation.
431 if (this.get(PAUSED)) {
433 } else if (!this.get(RUNNING)) {
440 * Pauses the animation and
441 * freezes it in its current state and time.
442 * Calling run() will continue where it left off.
447 if (this.get(RUNNING)) {
454 * Stops the animation and resets its time.
456 * @param {Boolean} finish If true, the animation will move to the last frame
459 stop: function(finish) {
460 if (this.get(RUNNING) || this.get(PAUSED)) {
469 this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
470 this._actualFrames = 0;
471 if (!this.get(PAUSED)) {
472 this._initAnimAttr();
474 _running[Y.stamp(this)] = this;
475 Y.Anim._startTimer();
481 this._set(START_TIME, null);
482 this._set(PAUSED, true);
483 delete _running[Y.stamp(this)];
487 * @description fires when an animation is paused.
488 * @param {Event} ev The pause event.
494 _resume: function() {
495 this._set(PAUSED, false);
496 _running[Y.stamp(this)] = this;
497 this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
498 Y.Anim._startTimer();
502 * @description fires when an animation is resumed (run from pause).
503 * @param {Event} ev The pause event.
509 _end: function(finish) {
510 var duration = this.get('duration') * 1000;
511 if (finish) { // jump to last frame
512 this._runAttrs(duration, duration, this.get(REVERSE));
515 this._set(START_TIME, null);
516 this._set(ELAPSED_TIME, 0);
517 this._set(PAUSED, false);
519 delete _running[Y.stamp(this)];
520 this.fire(END, {elapsed: this.get(ELAPSED_TIME)});
523 _runFrame: function() {
524 var d = this._runtimeAttr.duration,
525 t = new Date() - this.get(START_TIME),
526 reverse = this.get(REVERSE),
531 this._runAttrs(t, d, reverse);
532 this._actualFrames += 1;
533 this._set(ELAPSED_TIME, t);
541 _runAttrs: function(t, d, reverse) {
542 var attr = this._runtimeAttr,
543 customAttr = Y.Anim.behaviors,
544 easing = attr.easing,
563 setter = (i in customAttr && 'set' in customAttr[i]) ?
564 customAttr[i].set : Y.Anim.DEFAULT_SETTER;
567 setter(this, i, attribute.from, attribute.to, t, d, easing, attribute.unit);
569 setter(this, i, attribute.from, attribute.to, lastFrame, d, easing, attribute.unit);
577 _lastFrame: function() {
578 var iter = this.get('iterations'),
579 iterCount = this.get(ITERATION_COUNT);
582 if (iter === 'infinite' || iterCount < iter) {
583 if (this.get('direction') === 'alternate') {
584 this.set(REVERSE, !this.get(REVERSE)); // flip it
588 * @description fires when an animation begins an iteration.
589 * @param {Event} ev The iteration event.
592 this.fire('iteration');
598 this._set(START_TIME, new Date());
599 this._set(ITERATION_COUNT, iterCount);
602 _initAnimAttr: function() {
603 var from = this.get('from') || {},
604 to = this.get('to') || {},
606 duration: this.get('duration') * 1000,
607 easing: this.get('easing')
609 customAttr = Y.Anim.behaviors,
610 node = this.get(NODE), // implicit attr init
613 Y.each(to, function(val, name) {
614 if (typeof val === 'function') {
615 val = val.call(this, node);
619 if (begin === undefined) {
620 begin = (name in customAttr && 'get' in customAttr[name]) ?
621 customAttr[name].get(this, name) : Y.Anim.DEFAULT_GETTER(this, name);
622 } else if (typeof begin === 'function') {
623 begin = begin.call(this, node);
626 var mFrom = Y.Anim.RE_UNITS.exec(begin);
627 var mTo = Y.Anim.RE_UNITS.exec(val);
629 begin = mFrom ? mFrom[1] : begin;
630 end = mTo ? mTo[1] : val;
631 unit = mTo ? mTo[2] : mFrom ? mFrom[2] : ''; // one might be zero TODO: mixed units
633 if (!unit && Y.Anim.RE_DEFAULT_UNIT.test(name)) {
634 unit = Y.Anim.DEFAULT_UNIT;
637 if (!begin || !end) {
638 Y.error('invalid "from" or "to" for "' + name + '"', 'Anim');
650 this._runtimeAttr = attr;
654 // TODO: move to computedStyle? (browsers dont agree on default computed offsets)
655 _getOffset: function(attr) {
656 var node = this._node,
657 val = node.getComputedStyle(attr),
658 get = (attr === 'left') ? 'getX': 'getY',
659 set = (attr === 'left') ? 'setX': 'setY';
661 if (val === 'auto') {
662 var position = node.getStyle('position');
663 if (position === 'absolute' || position === 'fixed') {
674 destructor: function() {
675 delete Y.Anim._instances[Y.stamp(this)];
679 Y.extend(Y.Anim, Y.Base, proto);
682 }, '3.5.0' ,{requires:['base-base', 'node-style']});