2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('anim-base', function (Y, NAME) {
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',
68 Y.Anim.superclass.constructor.apply(this, arguments);
69 Y.Anim._instances[Y.stamp(this)] = this;
74 Y.Anim._instances = {};
77 * Regex of properties that should use the default unit.
79 * @property RE_DEFAULT_UNIT
82 Y.Anim.RE_DEFAULT_UNIT = /^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i;
85 * The default unit to use with properties that pass the RE_DEFAULT_UNIT test.
87 * @property DEFAULT_UNIT
90 Y.Anim.DEFAULT_UNIT = 'px';
92 Y.Anim.DEFAULT_EASING = function (t, b, c, d) {
93 return c * t / d + b; // linear easing
97 * Time in milliseconds passed to setInterval for frame processing
99 * @property intervalTime
103 Y.Anim._intervalTime = 20;
106 * Bucket for custom getters and setters
108 * @property behaviors
113 get: function(anim, attr) {
114 return anim._getOffset(attr);
119 Y.Anim.behaviors.top = Y.Anim.behaviors.left;
122 * The default setter to use when setting object properties.
124 * @property DEFAULT_SETTER
127 Y.Anim.DEFAULT_SETTER = function(anim, att, from, to, elapsed, duration, fn, unit) {
128 var node = anim._node,
129 domNode = node._node,
130 val = fn(elapsed, NUM(from), NUM(to) - NUM(from), duration);
133 if ('style' in domNode && (att in domNode.style || att in Y.DOM.CUSTOM_STYLES)) {
135 node.setStyle(att, val + unit);
136 } else if ('attributes' in domNode && att in domNode.attributes) {
137 node.setAttribute(att, val);
138 } else if (att in domNode) {
141 } else if (node.set) {
143 } else if (att in node) {
149 * The default getter to use when getting object properties.
151 * @property DEFAULT_GETTER
154 Y.Anim.DEFAULT_GETTER = function(anim, att) {
155 var node = anim._node,
156 domNode = node._node,
160 if ('style' in domNode && (att in domNode.style || att in Y.DOM.CUSTOM_STYLES)) {
161 val = node.getComputedStyle(att);
162 } else if ('attributes' in domNode && att in domNode.attributes) {
163 val = node.getAttribute(att);
164 } else if (att in domNode) {
167 } else if (node.get) {
169 } else if (att in node) {
178 * The object to be animated.
183 setter: function(node) {
185 if (typeof node === 'string' || node.nodeType) {
198 * The length of the animation. Defaults to "1" (second).
199 * @attribute duration
207 * The method that will provide values to the attribute(s) during the animation.
208 * Defaults to "Easing.easeNone".
213 value: Y.Anim.DEFAULT_EASING,
215 setter: function(val) {
216 if (typeof val === 'string' && Y.Easing) {
217 return Y.Easing[val];
223 * The starting values for the animated properties.
225 * Fields may be strings, numbers, or functions.
226 * If a function is used, the return value becomes the from value.
227 * If no from value is specified, the DEFAULT_GETTER will be used.
228 * Supports any unit, provided it matches the "to" (or default)
229 * unit (e.g. `{width: '10em', color: 'rgb(0, 0, 0)', borderColor: '#ccc'}`).
231 * If using the default ('px' for length-based units), the unit may be omitted
232 * (e.g. `{width: 100}, borderColor: 'ccc'}`, which defaults to pixels
233 * and hex, respectively).
241 * The ending values for the animated properties.
243 * Fields may be strings, numbers, or functions.
244 * Supports any unit, provided it matches the "from" (or default)
245 * unit (e.g. `{width: '50%', color: 'red', borderColor: '#ccc'}`).
247 * If using the default ('px' for length-based units), the unit may be omitted
248 * (e.g. `{width: 100, borderColor: 'ccc'}`, which defaults to pixels
249 * and hex, respectively).
257 * Date stamp for the first frame of the animation.
258 * @attribute startTime
269 * Current time the animation has been running.
270 * @attribute elapsedTime
281 * Whether or not the animation is currently running.
289 return !!_running[Y.stamp(this)];
296 * The number of times the animation should run
297 * @attribute iterations
306 * The number of iterations that have occurred.
307 * Resets when an animation ends (reaches iteration count or stop() called).
308 * @attribute iterationCount
319 * How iterations of the animation should behave.
320 * Possible values are "normal" and "alternate".
321 * Normal will repeat the animation, alternate will reverse on every other pass.
323 * @attribute direction
328 value: 'normal' // | alternate (fwd on odd, rev on even per spec)
332 * Whether or not the animation is currently paused.
344 * If true, animation begins from last frame
357 * Runs all animation instances.
361 Y.Anim.run = function() {
362 var instances = Y.Anim._instances,
364 for (i in instances) {
365 if (instances[i].run) {
372 * Pauses all animation instances.
376 Y.Anim.pause = function() {
377 for (var i in _running) { // stop timer if nothing running
378 if (_running[i].pause) {
387 * Stops all animation instances.
391 Y.Anim.stop = function() {
392 for (var i in _running) { // stop timer if nothing running
393 if (_running[i].stop) {
400 Y.Anim._startTimer = function() {
402 _timer = setInterval(Y.Anim._runFrame, Y.Anim._intervalTime);
406 Y.Anim._stopTimer = function() {
407 clearInterval(_timer);
412 * Called per Interval to handle each animation frame.
417 Y.Anim._runFrame = function() {
420 for (anim in _running) {
421 if (_running[anim]._runFrame) {
423 _running[anim]._runFrame();
432 Y.Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;
436 * Starts or resumes an animation.
441 if (this.get(PAUSED)) {
443 } else if (!this.get(RUNNING)) {
450 * Pauses the animation and
451 * freezes it in its current state and time.
452 * Calling run() will continue where it left off.
457 if (this.get(RUNNING)) {
464 * Stops the animation and resets its time.
466 * @param {Boolean} finish If true, the animation will move to the last frame
469 stop: function(finish) {
470 if (this.get(RUNNING) || this.get(PAUSED)) {
479 this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
480 this._actualFrames = 0;
481 if (!this.get(PAUSED)) {
482 this._initAnimAttr();
484 _running[Y.stamp(this)] = this;
485 Y.Anim._startTimer();
491 this._set(START_TIME, null);
492 this._set(PAUSED, true);
493 delete _running[Y.stamp(this)];
497 * @description fires when an animation is paused.
498 * @param {Event} ev The pause event.
504 _resume: function() {
505 this._set(PAUSED, false);
506 _running[Y.stamp(this)] = this;
507 this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
508 Y.Anim._startTimer();
512 * @description fires when an animation is resumed (run from pause).
513 * @param {Event} ev The pause event.
519 _end: function(finish) {
520 var duration = this.get('duration') * 1000;
521 if (finish) { // jump to last frame
522 this._runAttrs(duration, duration, this.get(REVERSE));
525 this._set(START_TIME, null);
526 this._set(ELAPSED_TIME, 0);
527 this._set(PAUSED, false);
529 delete _running[Y.stamp(this)];
530 this.fire(END, {elapsed: this.get(ELAPSED_TIME)});
533 _runFrame: function() {
534 var d = this._runtimeAttr.duration,
535 t = new Date() - this.get(START_TIME),
536 reverse = this.get(REVERSE),
539 this._runAttrs(t, d, reverse);
540 this._actualFrames += 1;
541 this._set(ELAPSED_TIME, t);
549 _runAttrs: function(t, d, reverse) {
550 var attr = this._runtimeAttr,
551 customAttr = Y.Anim.behaviors,
552 easing = attr.easing,
571 setter = (i in customAttr && 'set' in customAttr[i]) ?
572 customAttr[i].set : Y.Anim.DEFAULT_SETTER;
575 setter(this, i, attribute.from, attribute.to, t, d, easing, attribute.unit);
577 setter(this, i, attribute.from, attribute.to, lastFrame, d, easing, attribute.unit);
585 _lastFrame: function() {
586 var iter = this.get('iterations'),
587 iterCount = this.get(ITERATION_COUNT);
590 if (iter === 'infinite' || iterCount < iter) {
591 if (this.get('direction') === 'alternate') {
592 this.set(REVERSE, !this.get(REVERSE)); // flip it
596 * @description fires when an animation begins an iteration.
597 * @param {Event} ev The iteration event.
600 this.fire('iteration');
606 this._set(START_TIME, new Date());
607 this._set(ITERATION_COUNT, iterCount);
610 _initAnimAttr: function() {
611 var from = this.get('from') || {},
612 to = this.get('to') || {},
614 duration: this.get('duration') * 1000,
615 easing: this.get('easing')
617 customAttr = Y.Anim.behaviors,
618 node = this.get(NODE), // implicit attr init
621 Y.each(to, function(val, name) {
622 if (typeof val === 'function') {
623 val = val.call(this, node);
627 if (begin === undefined) {
628 begin = (name in customAttr && 'get' in customAttr[name]) ?
629 customAttr[name].get(this, name) : Y.Anim.DEFAULT_GETTER(this, name);
630 } else if (typeof begin === 'function') {
631 begin = begin.call(this, node);
634 var mFrom = Y.Anim.RE_UNITS.exec(begin),
635 mTo = Y.Anim.RE_UNITS.exec(val);
637 begin = mFrom ? mFrom[1] : begin;
638 end = mTo ? mTo[1] : val;
639 unit = mTo ? mTo[2] : mFrom ? mFrom[2] : ''; // one might be zero TODO: mixed units
641 if (!unit && Y.Anim.RE_DEFAULT_UNIT.test(name)) {
642 unit = Y.Anim.DEFAULT_UNIT;
645 if (!begin || !end) {
646 Y.error('invalid "from" or "to" for "' + name + '"', 'Anim');
651 from: Y.Lang.isObject(begin) ? Y.clone(begin) : begin,
658 this._runtimeAttr = attr;
662 // TODO: move to computedStyle? (browsers dont agree on default computed offsets)
663 _getOffset: function(attr) {
664 var node = this._node,
665 val = node.getComputedStyle(attr),
666 get = (attr === 'left') ? 'getX': 'getY',
667 set = (attr === 'left') ? 'setX': 'setY',
670 if (val === 'auto') {
671 position = node.getStyle('position');
672 if (position === 'absolute' || position === 'fixed') {
683 destructor: function() {
684 delete Y.Anim._instances[Y.stamp(this)];
688 Y.extend(Y.Anim, Y.Base, proto);
691 }, '3.13.0', {"requires": ["base-base", "node-style"]});