- /**
- * The Animation Utility provides an API for creating advanced transitions.
- * @module anim
- */
-
- /**
- * Provides the base Anim class, for animating numeric properties.
- *
- * @module anim
- * @submodule anim-base
- */
-
- /**
- * A class for constructing animation instances.
- * @class Anim
- * @for Anim
- * @constructor
- * @extends Base
- */
-
- var RUNNING = 'running',
- START_TIME = 'startTime',
- ELAPSED_TIME = 'elapsedTime',
- /**
- * @for Anim
- * @event start
- * @description fires when an animation begins.
- * @param {Event} ev The start event.
- * @type Event.Custom
- */
- START = 'start',
-
- /**
- * @event tween
- * @description fires every frame of the animation.
- * @param {Event} ev The tween event.
- * @type Event.Custom
- */
- TWEEN = 'tween',
-
- /**
- * @event end
- * @description fires after the animation completes.
- * @param {Event} ev The end event.
- * @type Event.Custom
- */
- END = 'end',
- NODE = 'node',
- PAUSED = 'paused',
- REVERSE = 'reverse', // TODO: cleanup
- ITERATION_COUNT = 'iterationCount',
-
- NUM = Number;
-
- var _running = {},
- _timer;
-
- Y.Anim = function() {
- Y.Anim.superclass.constructor.apply(this, arguments);
- Y.Anim._instances[Y.stamp(this)] = this;
- };
-
- Y.Anim.NAME = 'anim';
-
- Y.Anim._instances = {};
-
- /**
- * Regex of properties that should use the default unit.
- *
- * @property RE_DEFAULT_UNIT
- * @static
- */
- Y.Anim.RE_DEFAULT_UNIT = /^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i;
-
- /**
- * The default unit to use with properties that pass the RE_DEFAULT_UNIT test.
- *
- * @property DEFAULT_UNIT
- * @static
- */
- Y.Anim.DEFAULT_UNIT = 'px';
-
- Y.Anim.DEFAULT_EASING = function (t, b, c, d) {
- return c * t / d + b; // linear easing
- };
-
- /**
- * Time in milliseconds passed to setInterval for frame processing
- *
- * @property intervalTime
- * @default 20
- * @static
- */
- Y.Anim._intervalTime = 20;
-
- /**
- * Bucket for custom getters and setters
- *
- * @property behaviors
- * @static
- */
- Y.Anim.behaviors = {
- left: {
- get: function(anim, attr) {
- return anim._getOffset(attr);
- }
- }
- };
-
- Y.Anim.behaviors.top = Y.Anim.behaviors.left;
-
- /**
- * The default setter to use when setting object properties.
- *
- * @property DEFAULT_SETTER
- * @static
- */
- Y.Anim.DEFAULT_SETTER = function(anim, att, from, to, elapsed, duration, fn, unit) {
- var node = anim._node,
- domNode = node._node,
- val = fn(elapsed, NUM(from), NUM(to) - NUM(from), duration);
-
- if (domNode) {
- if ('style' in domNode && (att in domNode.style || att in Y.DOM.CUSTOM_STYLES)) {
- unit = unit || '';
- node.setStyle(att, val + unit);
- } else if ('attributes' in domNode && att in domNode.attributes) {
- node.setAttribute(att, val);
- } else if (att in domNode) {
- domNode[att] = val;
- }
- } else if (node.set) {
- node.set(att, val);
- } else if (att in node) {
- node[att] = val;
- }
- };
-
- /**
- * The default getter to use when getting object properties.
- *
- * @property DEFAULT_GETTER
- * @static
- */
- Y.Anim.DEFAULT_GETTER = function(anim, att) {
- var node = anim._node,
- domNode = node._node,
- val = '';
-
- if (domNode) {
- if ('style' in domNode && (att in domNode.style || att in Y.DOM.CUSTOM_STYLES)) {
- val = node.getComputedStyle(att);
- } else if ('attributes' in domNode && att in domNode.attributes) {
- val = node.getAttribute(att);
- } else if (att in domNode) {
- val = domNode[att];
- }
- } else if (node.get) {
- val = node.get(att);
- } else if (att in node) {
- val = node[att];
- }
-
- return val;
- };
-
- Y.Anim.ATTRS = {
- /**
- * The object to be animated.
- * @attribute node
- * @type Node
- */
- node: {
- setter: function(node) {
- if (node) {
- if (typeof node === 'string' || node.nodeType) {
- node = Y.one(node);
- }
- }
-
- this._node = node;
- if (!node) {
- Y.log(node + ' is not a valid node', 'warn', 'Anim');
- }
- return node;
- }
- },
-
- /**
- * The length of the animation. Defaults to "1" (second).
- * @attribute duration
- * @type NUM
- */
- duration: {
- value: 1
- },
-
- /**
- * The method that will provide values to the attribute(s) during the animation.
- * Defaults to "Easing.easeNone".
- * @attribute easing
- * @type Function
- */
- easing: {
- value: Y.Anim.DEFAULT_EASING,
-
- setter: function(val) {
- if (typeof val === 'string' && Y.Easing) {
- return Y.Easing[val];
- }
- }
- },
-
- /**
- * The starting values for the animated properties.
- *
- * Fields may be strings, numbers, or functions.
- * If a function is used, the return value becomes the from value.
- * If no from value is specified, the DEFAULT_GETTER will be used.
- * Supports any unit, provided it matches the "to" (or default)
- * unit (e.g. `{width: '10em', color: 'rgb(0, 0, 0)', borderColor: '#ccc'}`).
- *
- * If using the default ('px' for length-based units), the unit may be omitted
- * (e.g. `{width: 100}, borderColor: 'ccc'}`, which defaults to pixels
- * and hex, respectively).
- *
- * @attribute from
- * @type Object
- */
- from: {},
-
- /**
- * The ending values for the animated properties.
- *
- * Fields may be strings, numbers, or functions.
- * Supports any unit, provided it matches the "from" (or default)
- * unit (e.g. `{width: '50%', color: 'red', borderColor: '#ccc'}`).
- *
- * If using the default ('px' for length-based units), the unit may be omitted
- * (e.g. `{width: 100, borderColor: 'ccc'}`, which defaults to pixels
- * and hex, respectively).
- *
- * @attribute to
- * @type Object
- */
- to: {},
-
- /**
- * Date stamp for the first frame of the animation.
- * @attribute startTime
- * @type Int
- * @default 0
- * @readOnly
- */
- startTime: {
- value: 0,
- readOnly: true
- },
-
- /**
- * Current time the animation has been running.
- * @attribute elapsedTime
- * @type Int
- * @default 0
- * @readOnly
- */
- elapsedTime: {
- value: 0,
- readOnly: true
- },
-
- /**
- * Whether or not the animation is currently running.
- * @attribute running
- * @type Boolean
- * @default false
- * @readOnly
- */
- running: {
- getter: function() {
- return !!_running[Y.stamp(this)];
- },
- value: false,
- readOnly: true
- },
-
- /**
- * The number of times the animation should run
- * @attribute iterations
- * @type Int
- * @default 1
- */
- iterations: {
- value: 1
- },
-
- /**
- * The number of iterations that have occurred.
- * Resets when an animation ends (reaches iteration count or stop() called).
- * @attribute iterationCount
- * @type Int
- * @default 0
- * @readOnly
- */
- iterationCount: {
- value: 0,
- readOnly: true
- },
-
- /**
- * How iterations of the animation should behave.
- * Possible values are "normal" and "alternate".
- * Normal will repeat the animation, alternate will reverse on every other pass.
- *
- * @attribute direction
- * @type String
- * @default "normal"
- */
- direction: {
- value: 'normal' // | alternate (fwd on odd, rev on even per spec)
- },
-
- /**
- * Whether or not the animation is currently paused.
- * @attribute paused
- * @type Boolean
- * @default false
- * @readOnly
- */
- paused: {
- readOnly: true,
- value: false
- },
-
- /**
- * If true, the `from` and `to` attributes are swapped,
- * and the animation is then run starting from `from`.
- * @attribute reverse
- * @type Boolean
- * @default false
- */
- reverse: {
- value: false
- }
-
-
- };
-
- /**
- * Runs all animation instances.
- * @method run
- * @static
- */
- Y.Anim.run = function() {
- var instances = Y.Anim._instances,
- i;
- for (i in instances) {
- if (instances[i].run) {
- instances[i].run();
- }
- }
- };
-
- /**
- * Pauses all animation instances.
- * @method pause
- * @static
- */
- Y.Anim.pause = function() {
- for (var i in _running) { // stop timer if nothing running
- if (_running[i].pause) {
- _running[i].pause();
- }
- }
-
- Y.Anim._stopTimer();
- };
-
- /**
- * Stops all animation instances.
- * @method stop
- * @static
- */
- Y.Anim.stop = function() {
- for (var i in _running) { // stop timer if nothing running
- if (_running[i].stop) {
- _running[i].stop();
- }
- }
- Y.Anim._stopTimer();
- };
-
- Y.Anim._startTimer = function() {
- if (!_timer) {
- _timer = setInterval(Y.Anim._runFrame, Y.Anim._intervalTime);
- }
- };
-
- Y.Anim._stopTimer = function() {
- clearInterval(_timer);
- _timer = 0;
- };
-
- /**
- * Called per Interval to handle each animation frame.
- * @method _runFrame
- * @private
- * @static
- */
- Y.Anim._runFrame = function() {
- var done = true,
- anim;
- for (anim in _running) {
- if (_running[anim]._runFrame) {
- done = false;
- _running[anim]._runFrame();
- }
- }
-
- if (done) {
- Y.Anim._stopTimer();
- }
- };
-
- Y.Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;
-
- var proto = {
- /**
- * Starts or resumes an animation.
- * @method run
- * @chainable
- */
- run: function() {
- if (this.get(PAUSED)) {
- this._resume();
- } else if (!this.get(RUNNING)) {
- this._start();
- }
- return this;
- },
-
- /**
- * Pauses the animation and
- * freezes it in its current state and time.
- * Calling run() will continue where it left off.
- * @method pause
- * @chainable
- */
- pause: function() {
- if (this.get(RUNNING)) {
- this._pause();
- }
- return this;
- },
-
- /**
- * Stops the animation and resets its time.
- * @method stop
- * @param {Boolean} finish If true, the animation will move to the last frame
- * @chainable
- */
- stop: function(finish) {
- if (this.get(RUNNING) || this.get(PAUSED)) {
- this._end(finish);
- }
- return this;
- },
-
- _added: false,
-
- _start: function() {
- this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
- this._actualFrames = 0;
- if (!this.get(PAUSED)) {
- this._initAnimAttr();
- }
- _running[Y.stamp(this)] = this;
- Y.Anim._startTimer();
-
- this.fire(START);
- },
-
- _pause: function() {
- this._set(START_TIME, null);
- this._set(PAUSED, true);
- delete _running[Y.stamp(this)];
-
- /**
- * @event pause
- * @description fires when an animation is paused.
- * @param {Event} ev The pause event.
- * @type Event.Custom
- */
- this.fire('pause');
- },
-
- _resume: function() {
- this._set(PAUSED, false);
- _running[Y.stamp(this)] = this;
- this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
- Y.Anim._startTimer();
-
- /**
- * @event resume
- * @description fires when an animation is resumed (run from pause).
- * @param {Event} ev The pause event.
- * @type Event.Custom
- */
- this.fire('resume');
- },
-
- _end: function(finish) {
- var duration = this.get('duration') * 1000;
- if (finish) { // jump to last frame
- this._runAttrs(duration, duration, this.get(REVERSE));
- }
-
- this._set(START_TIME, null);
- this._set(ELAPSED_TIME, 0);
- this._set(PAUSED, false);
-
- delete _running[Y.stamp(this)];
- this.fire(END, {elapsed: this.get(ELAPSED_TIME)});
- },
-
- _runFrame: function() {
- var d = this._runtimeAttr.duration,
- t = new Date() - this.get(START_TIME),
- reverse = this.get(REVERSE),
- done = (t >= d);
-
- this._runAttrs(t, d, reverse);
- this._actualFrames += 1;
- this._set(ELAPSED_TIME, t);
-
- this.fire(TWEEN);
- if (done) {
- this._lastFrame();
- }
- },
-
- _runAttrs: function(t, d, reverse) {
- var attr = this._runtimeAttr,
- customAttr = Y.Anim.behaviors,
- easing = attr.easing,
- lastFrame = d,
- done = false,
- attribute,
- setter,
- i;
-
- if (t >= d) {
- done = true;
- }
-
- if (reverse) {
- t = d - t;
- lastFrame = 0;
- }
-
- for (i in attr) {
- if (attr[i].to) {
- attribute = attr[i];
- setter = (i in customAttr && 'set' in customAttr[i]) ?
- customAttr[i].set : Y.Anim.DEFAULT_SETTER;
-
- if (!done) {
- setter(this, i, attribute.from, attribute.to, t, d, easing, attribute.unit);
- } else {
- setter(this, i, attribute.from, attribute.to, lastFrame, d, easing, attribute.unit);
- }
- }
- }
-
-
- },
-
- _lastFrame: function() {
- var iter = this.get('iterations'),
- iterCount = this.get(ITERATION_COUNT);
-
- iterCount += 1;
- if (iter === 'infinite' || iterCount < iter) {
- if (this.get('direction') === 'alternate') {
- this.set(REVERSE, !this.get(REVERSE)); // flip it
- }
- /**
- * @event iteration
- * @description fires when an animation begins an iteration.
- * @param {Event} ev The iteration event.
- * @type Event.Custom
- */
- this.fire('iteration');
- } else {
- iterCount = 0;
- this._end();
- }
-
- this._set(START_TIME, new Date());
- this._set(ITERATION_COUNT, iterCount);
- },
-
- _initAnimAttr: function() {
- var from = this.get('from') || {},
- to = this.get('to') || {},
- attr = {
- duration: this.get('duration') * 1000,
- easing: this.get('easing')
- },
- customAttr = Y.Anim.behaviors,
- node = this.get(NODE), // implicit attr init
- unit, begin, end;
-
- Y.each(to, function(val, name) {
- if (typeof val === 'function') {
- val = val.call(this, node);
- }
-
- begin = from[name];
- if (begin === undefined) {
- begin = (name in customAttr && 'get' in customAttr[name]) ?
- customAttr[name].get(this, name) : Y.Anim.DEFAULT_GETTER(this, name);
- } else if (typeof begin === 'function') {
- begin = begin.call(this, node);
- }
-
- var mFrom = Y.Anim.RE_UNITS.exec(begin),
- mTo = Y.Anim.RE_UNITS.exec(val);
-
- begin = mFrom ? mFrom[1] : begin;
- end = mTo ? mTo[1] : val;
- unit = mTo ? mTo[2] : mFrom ? mFrom[2] : ''; // one might be zero TODO: mixed units
-
- if (!unit && Y.Anim.RE_DEFAULT_UNIT.test(name)) {
- unit = Y.Anim.DEFAULT_UNIT;
- }
-
- if (!begin || !end) {
- Y.error('invalid "from" or "to" for "' + name + '"', 'Anim');
- return;
- }
-
- attr[name] = {
- from: Y.Lang.isObject(begin) ? Y.clone(begin) : begin,
- to: end,
- unit: unit
- };
-
- }, this);
-
- this._runtimeAttr = attr;
- },
-
-
- // TODO: move to computedStyle? (browsers dont agree on default computed offsets)
- _getOffset: function(attr) {
- var node = this._node,
- val = node.getComputedStyle(attr),
- get = (attr === 'left') ? 'getX': 'getY',
- set = (attr === 'left') ? 'setX': 'setY',
- position;
-
- if (val === 'auto') {
- position = node.getStyle('position');
- if (position === 'absolute' || position === 'fixed') {
- val = node[get]();
- node[set](val);
- } else {
- val = 0;
- }
- }
-
- return val;
- },
-
- destructor: function() {
- delete Y.Anim._instances[Y.stamp(this)];
- }
- };
-
- Y.extend(Y.Anim, Y.Base, proto);
-
-