/**
* @class Ext.fx.Animator
* Animation instance
*/
Ext.define('Ext.fx.Animator', {
/* Begin Definitions */
mixins: {
observable: 'Ext.util.Observable'
},
requires: ['Ext.fx.Manager'],
/* End Definitions */
/**
* @cfg {Number} duration
* Time in milliseconds for the animation to last. Defaults to 250.
*/
duration: 250,
/**
* @cfg {Number} delay
* Time to delay before starting the animation. Defaults to 0.
*/
delay: 0,
/**
* @cfg {String} easing
* A valid Ext.fx.Easing or Ext.fx.EasingPseudo value for the effect.
* Easing is now calculated exclusively with the use of cubic-bezier curves and follows the
* CSS3
* specification for 'transition-timing-function'. Defaults to ease
* Standard CSS3 Easing Values:
*
* - ease: The ease function is equivalent to cubic-bezier(0.25, 0.1, 0.25, 1.0).
* - linear: The linear function is equivalent to cubic-bezier(0.0, 0.0, 1.0, 1.0).
* - ease-in: The ease-in function is equivalent to cubic-bezier(0.42, 0, 1.0, 1.0).
* - ease-out: The ease-out function is equivalent to cubic-bezier(0, 0, 0.58, 1.0).
* - ease-in-out: The ease-in-out function is equivalent to cubic-bezier(0.42, 0, 0.58, 1.0)
* - cubic-bezier: Specifies a cubic-bezier curve. The four values specify points P1 and P2 of
* the curve as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
*
* PseudoEasing combines multiple cubic-bezier curves and creates an Ext.fx.Animation to achieve more complex effects.
* Extended Pseudo Easing Values:
*
* - back-in
* - back-out
* - bounce-in
* - bounce-out
* - elastic-in
* - elastic-out
*
*/
easing: 'ease',
/**
* @private
*/
damper: 1,
/**
* @cfg {Number} iterations
* Number of times to execute the animation. Defaults to 1.
*/
iterations: 1,
/**
* Current keyframe step of the animation.
* @property keyframeStep
* @type Number
*/
keyframeStep: 0,
/**
* @private
*/
animKeyFramesRE: /^(from|to|\d+%?)$/,
/**
* @cfg {Ext.fx.target} target
* The Ext.fx.target to apply the animation to. If not specified during initialization, this can be passed to the applyAnimator
* method to apply the same animation to many targets.
*/
/**
* @cfg {Object} keyframes
* Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
* is considered '100%'.Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
* "from" or "to". A keyframe declaration without these keyframe selectors is invalid and will not be available for
* animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
* be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
keyframes : {
'0%': {
left: 100
},
'40%': {
left: 150
},
'60%': {
left: 75
},
'100%': {
left: 100
}
}
*/
constructor: function(config) {
config = Ext.apply(this, config || {});
this.config = config;
this.id = Ext.id(null, 'ext-animator-');
this.addEvents(
/**
* @event beforeanimate
* Fires before the animation starts. A handler can return false to cancel the animation.
* @param {Ext.fx.Animator} this
*/
'beforeanimate',
/**
* @event keyframe
* Fires at each keyframe.
* @param {Ext.fx.Animator} this
* @param {Number} keyframe step number
*/
'keyframe',
/**
* @event afteranimate
* Fires when the animation is complete.
* @param {Ext.fx.Animator} this
* @param {Date} startTime
* @param {Date} elapsedTime
*/
'afteranimate'
);
this.mixins.observable.constructor.call(this, config);
this.timeline = [];
this.createTimeline(this.keyframes);
if (this.target) {
this.applyAnimator(this.target);
}
},
/**
* @private
*/
sorter: function (a, b) {
return a.pct - b.pct;
},
/**
* @private
* Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
* or applying the 'to' configuration to all keyframes. Also calculates the proper animation duration per keyframe.
*/
createTimeline: function(keyframes) {
var attrs = [],
attr,
to = this.to || {},
prevMs,
ms,
i,
ln,
pct,
anim,
nextAnim,
duration = this.duration;
if (keyframes.isPseudoEasing) {
this.isPseudoEasing = true;
}
for (pct in keyframes) {
if (keyframes.hasOwnProperty(pct) && this.animKeyFramesRE.test(pct)) {
attr = {attrs: Ext.apply(keyframes[pct], to)};
// CSS3 spec allow for from/to to be specified.
if (pct == "from") {
pct = 0;
}
else if (pct == "to") {
pct = 100;
}
// convert % values into integers
attr.pct = parseInt(pct, 10);
attrs.push(attr);
}
}
// Sort by pct property
attrs.sort(this.sorter);
// Only an end
//if (attrs[0].pct) {
// attrs.unshift({pct: 0, attrs: element.attrs});
//}
ln = attrs.length;
for (i = 0; i < ln; i++) {
prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
ms = duration * (attrs[i].pct / 100);
this.timeline.push({
duration: ms - prevMs,
attrs: attrs[i].attrs
});
}
},
/**
* Applies animation to the Ext.fx.target
* @param target
* @type string/object
*/
applyAnimator: function(target) {
target = Ext.fx.Manager.createTarget(target);
var anims = [],
anim,
timeline = this.timeline,
reverse = this.reverse,
isPseudoEasing = this.isPseudoEasing,
ln = timeline.length,
easing,
damper,
initial,
attrs,
lastAttrs,
i;
if (this.fireEvent('beforeanimate', this) !== false) {
for (i = 0; i < ln; i++) {
anim = timeline[i];
attrs = anim.attrs;
easing = attrs.easing || this.easing;
damper = attrs.damper || this.damper;
delete attrs.easing;
delete attrs.damper;
anim = new Ext.fx.Anim({
target: target,
easing: easing,
damper: damper,
initialFrom: isPseudoEasing && initial,
duration: anim.duration,
reverse: reverse,
paused: true,
from: lastAttrs,
to: attrs
});
if (!i) {
anim.initAttrs();
initial = anim.currentAttrs;
}
if (!isPseudoEasing) {
lastAttrs = Ext.apply({}, attrs, lastAttrs);
}
anims.push(anim);
}
if (reverse) {
anims.reverse();
}
for (i = 0; i < ln - 1; i++) {
anim = anims[i];
anim.nextAnim = anims[i + 1];
anim.on('afteranimate', function() {
this.nextAnim.paused = false;
});
anim.on('afteranimate', function() {
this.fireEvent('keyframe', this, ++this.keyframeStep);
}, this);
}
anims[ln - 1].on('afteranimate', function() {
this.fireEvent('afteranimate', this, this.startTime, new Date() - this.startTime);
}, this);
Ext.defer(function(anim) {
this.startAnimator(anim);
}, this.delay, this, [anims[0]]);
}
},
/**
* @private
*/
startAnimator: function(anim) {
this.startTime = new Date();
anim.paused = false;
}
});