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) {
189
* The length of the animation. Defaults to "1" (second).
190
* @attribute duration
198
* The method that will provide values to the attribute(s) during the animation.
199
* Defaults to "Easing.easeNone".
204
value: Y.Anim.DEFAULT_EASING,
206
setter: function(val) {
207
if (typeof val === 'string' && Y.Easing) {
208
return Y.Easing[val];
214
* The starting values for the animated properties.
216
* Fields may be strings, numbers, or functions.
217
* If a function is used, the return value becomes the from value.
218
* If no from value is specified, the DEFAULT_GETTER will be used.
219
* Supports any unit, provided it matches the "to" (or default)
220
* unit (e.g. `{width: '10em', color: 'rgb(0, 0 0)', borderColor: '#ccc'}`).
222
* If using the default ('px' for length-based units), the unit may be omitted
223
* (e.g. `{width: 100}, borderColor: 'ccc'}`, which defaults to pixels
224
* and hex, respectively).
232
* The ending values for the animated properties.
234
* Fields may be strings, numbers, or functions.
235
* Supports any unit, provided it matches the "from" (or default)
236
* unit (e.g. `{width: '50%', color: 'red', borderColor: '#ccc'}`).
238
* If using the default ('px' for length-based units), the unit may be omitted
239
* (e.g. `{width: 100, borderColor: 'ccc'}`, which defaults to pixels
240
* and hex, respectively).
248
* Date stamp for the first frame of the animation.
249
* @attribute startTime
260
* Current time the animation has been running.
261
* @attribute elapsedTime
272
* Whether or not the animation is currently running.
280
return !!_running[Y.stamp(this)];
287
* The number of times the animation should run
288
* @attribute iterations
297
* The number of iterations that have occurred.
298
* Resets when an animation ends (reaches iteration count or stop() called).
299
* @attribute iterationCount
310
* How iterations of the animation should behave.
311
* Possible values are "normal" and "alternate".
312
* Normal will repeat the animation, alternate will reverse on every other pass.
314
* @attribute direction
319
value: 'normal' // | alternate (fwd on odd, rev on even per spec)
323
* Whether or not the animation is currently paused.
335
* If true, animation begins from last frame
348
* Runs all animation instances.
352
Y.Anim.run = function() {
353
var instances = Y.Anim._instances;
354
for (var i in instances) {
355
if (instances[i].run) {
362
* Pauses all animation instances.
366
Y.Anim.pause = function() {
367
for (var i in _running) { // stop timer if nothing running
368
if (_running[i].pause) {
377
* Stops all animation instances.
381
Y.Anim.stop = function() {
382
for (var i in _running) { // stop timer if nothing running
383
if (_running[i].stop) {
390
Y.Anim._startTimer = function() {
392
_timer = setInterval(Y.Anim._runFrame, Y.Anim._intervalTime);
396
Y.Anim._stopTimer = function() {
397
clearInterval(_timer);
402
* Called per Interval to handle each animation frame.
407
Y.Anim._runFrame = function() {
409
for (var anim in _running) {
410
if (_running[anim]._runFrame) {
412
_running[anim]._runFrame();
421
Y.Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;
425
* Starts or resumes an animation.
430
if (this.get(PAUSED)) {
432
} else if (!this.get(RUNNING)) {
439
* Pauses the animation and
440
* freezes it in its current state and time.
441
* Calling run() will continue where it left off.
446
if (this.get(RUNNING)) {
453
* Stops the animation and resets its time.
455
* @param {Boolean} finish If true, the animation will move to the last frame
458
stop: function(finish) {
459
if (this.get(RUNNING) || this.get(PAUSED)) {
468
this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
469
this._actualFrames = 0;
470
if (!this.get(PAUSED)) {
471
this._initAnimAttr();
473
_running[Y.stamp(this)] = this;
474
Y.Anim._startTimer();
480
this._set(START_TIME, null);
481
this._set(PAUSED, true);
482
delete _running[Y.stamp(this)];
486
* @description fires when an animation is paused.
487
* @param {Event} ev The pause event.
493
_resume: function() {
494
this._set(PAUSED, false);
495
_running[Y.stamp(this)] = this;
496
this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
497
Y.Anim._startTimer();
501
* @description fires when an animation is resumed (run from pause).
502
* @param {Event} ev The pause event.
508
_end: function(finish) {
509
var duration = this.get('duration') * 1000;
510
if (finish) { // jump to last frame
511
this._runAttrs(duration, duration, this.get(REVERSE));
514
this._set(START_TIME, null);
515
this._set(ELAPSED_TIME, 0);
516
this._set(PAUSED, false);
518
delete _running[Y.stamp(this)];
519
this.fire(END, {elapsed: this.get(ELAPSED_TIME)});
522
_runFrame: function() {
523
var d = this._runtimeAttr.duration,
524
t = new Date() - this.get(START_TIME),
525
reverse = this.get(REVERSE),
530
this._runAttrs(t, d, reverse);
531
this._actualFrames += 1;
532
this._set(ELAPSED_TIME, t);
540
_runAttrs: function(t, d, reverse) {
541
var attr = this._runtimeAttr,
542
customAttr = Y.Anim.behaviors,
543
easing = attr.easing,
562
setter = (i in customAttr && 'set' in customAttr[i]) ?
563
customAttr[i].set : Y.Anim.DEFAULT_SETTER;
566
setter(this, i, attribute.from, attribute.to, t, d, easing, attribute.unit);
568
setter(this, i, attribute.from, attribute.to, lastFrame, d, easing, attribute.unit);
576
_lastFrame: function() {
577
var iter = this.get('iterations'),
578
iterCount = this.get(ITERATION_COUNT);
581
if (iter === 'infinite' || iterCount < iter) {
582
if (this.get('direction') === 'alternate') {
583
this.set(REVERSE, !this.get(REVERSE)); // flip it
587
* @description fires when an animation begins an iteration.
588
* @param {Event} ev The iteration event.
591
this.fire('iteration');
597
this._set(START_TIME, new Date());
598
this._set(ITERATION_COUNT, iterCount);
601
_initAnimAttr: function() {
602
var from = this.get('from') || {},
603
to = this.get('to') || {},
605
duration: this.get('duration') * 1000,
606
easing: this.get('easing')
608
customAttr = Y.Anim.behaviors,
609
node = this.get(NODE), // implicit attr init
612
Y.each(to, function(val, name) {
613
if (typeof val === 'function') {
614
val = val.call(this, node);
618
if (begin === undefined) {
619
begin = (name in customAttr && 'get' in customAttr[name]) ?
620
customAttr[name].get(this, name) : Y.Anim.DEFAULT_GETTER(this, name);
621
} else if (typeof begin === 'function') {
622
begin = begin.call(this, node);
625
var mFrom = Y.Anim.RE_UNITS.exec(begin);
626
var mTo = Y.Anim.RE_UNITS.exec(val);
628
begin = mFrom ? mFrom[1] : begin;
629
end = mTo ? mTo[1] : val;
630
unit = mTo ? mTo[2] : mFrom ? mFrom[2] : ''; // one might be zero TODO: mixed units
632
if (!unit && Y.Anim.RE_DEFAULT_UNIT.test(name)) {
633
unit = Y.Anim.DEFAULT_UNIT;
636
if (!begin || !end) {
637
Y.error('invalid "from" or "to" for "' + name + '"', 'Anim');
649
this._runtimeAttr = attr;
653
// TODO: move to computedStyle? (browsers dont agree on default computed offsets)
654
_getOffset: function(attr) {
655
var node = this._node,
656
val = node.getComputedStyle(attr),
657
get = (attr === 'left') ? 'getX': 'getY',
658
set = (attr === 'left') ? 'setX': 'setY';
660
if (val === 'auto') {
661
var position = node.getStyle('position');
662
if (position === 'absolute' || position === 'fixed') {
673
destructor: function() {
674
delete Y.Anim._instances[Y.stamp(this)];
678
Y.extend(Y.Anim, Y.Base, proto);
681
}, '3.5.1' ,{requires:['base-base', 'node-style']});