--- /dev/null
+/**
+ * @class Ext.fx.Animator
+ * Animation instance
+
+This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
+Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
+at various points throughout the animation.
+
+__Using Keyframes__
+The {@link #keyframes} option is the most important part of specifying an animation when using this
+class. A key frame is a point in a particular animation. We represent this as a percentage of the
+total animation duration. At each key frame, we can specify the target values at that time. Note that
+you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link keyframe}
+event that fires after each key frame is reached.
+
+__Example Usage__
+In the example below, we modify the values of the element at each fifth throughout the animation.
+
+ Ext.create('Ext.fx.Animator', {
+ target: Ext.getBody().createChild({
+ style: {
+ width: '100px',
+ height: '100px',
+ 'background-color': 'red'
+ }
+ }),
+ duration: 10000, // 10 seconds
+ keyframes: {
+ 0: {
+ opacity: 1,
+ backgroundColor: 'FF0000'
+ },
+ 20: {
+ x: 30,
+ opacity: 0.5
+ },
+ 40: {
+ x: 130,
+ backgroundColor: '0000FF'
+ },
+ 60: {
+ y: 80,
+ opacity: 0.3
+ },
+ 80: {
+ width: 200,
+ y: 200
+ },
+ 100: {
+ opacity: 1,
+ backgroundColor: '00FF00'
+ }
+ }
+ });
+
+ * @markdown
+ */
+Ext.define('Ext.fx.Animator', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: ['Ext.fx.Manager'],
+
+ /* End Definitions */
+
+ isAnimator: true,
+
+ /**
+ * @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,
+
+ /* private used to track a delayed starting time */
+ delayStart: 0,
+
+ /**
+ * @cfg {Boolean} dynamic
+ * Currently only for Component Animation: Only set a component's outer element size bypassing layouts. Set to true to do full layouts for every frame of the animation. Defaults to false.
+ */
+ dynamic: false,
+
+ /**
+ * @cfg {String} easing
+
+This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
+speed over its duration.
+
+- backIn
+- backOut
+- bounceIn
+- bounceOut
+- ease
+- easeIn
+- easeOut
+- easeInOut
+- elasticIn
+- elasticOut
+- cubic-bezier(x1, y1, x2, y2)
+
+Note that cubic-bezier will create a custom easing curve following the CSS3 transition-timing-function specification `{@link http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag}`. 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.
+
+ * @markdown
+ */
+ easing: 'ease',
+
+ /**
+ * Flag to determine if the animation has started
+ * @property running
+ * @type boolean
+ */
+ running: false,
+
+ /**
+ * Flag to determine if the animation is paused. Only set this to true if you need to
+ * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
+ * @property paused
+ * @type boolean
+ */
+ paused: false,
+
+ /**
+ * @private
+ */
+ damper: 1,
+
+ /**
+ * @cfg {Number} iterations
+ * Number of times to execute the animation. Defaults to 1.
+ */
+ iterations: 1,
+
+ /**
+ * Current iteration the animation is running.
+ * @property currentIteration
+ * @type int
+ */
+ currentIteration: 0,
+
+ /**
+ * 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%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
+ * "from" or "to"</b>. 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:
+ <pre><code>
+keyframes : {
+ '0%': {
+ left: 100
+ },
+ '40%': {
+ left: 150
+ },
+ '60%': {
+ left: 75
+ },
+ '100%': {
+ left: 100
+ }
+}
+ </code></pre>
+ */
+ constructor: function(config) {
+ var me = this;
+ config = Ext.apply(me, config || {});
+ me.config = config;
+ me.id = Ext.id(null, 'ext-animator-');
+ me.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
+ */
+ 'afteranimate'
+ );
+ me.mixins.observable.constructor.call(me, config);
+ me.timeline = [];
+ me.createTimeline(me.keyframes);
+ if (me.target) {
+ me.applyAnimator(me.target);
+ Ext.fx.Manager.addAnim(me);
+ }
+ },
+
+ /**
+ * @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 me = this,
+ attrs = [],
+ to = me.to || {},
+ duration = me.duration,
+ prevMs, ms, i, ln, pct, anim, nextAnim, attr;
+
+ for (pct in keyframes) {
+ if (keyframes.hasOwnProperty(pct) && me.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
+ Ext.Array.sort(attrs, me.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);
+ me.timeline.push({
+ duration: ms - prevMs,
+ attrs: attrs[i].attrs
+ });
+ }
+ },
+
+ /**
+ * Applies animation to the Ext.fx.target
+ * @private
+ * @param target
+ * @type string/object
+ */
+ applyAnimator: function(target) {
+ var me = this,
+ anims = [],
+ timeline = me.timeline,
+ reverse = me.reverse,
+ ln = timeline.length,
+ anim, easing, damper, initial, attrs, lastAttrs, i;
+
+ if (me.fireEvent('beforeanimate', me) !== false) {
+ for (i = 0; i < ln; i++) {
+ anim = timeline[i];
+ attrs = anim.attrs;
+ easing = attrs.easing || me.easing;
+ damper = attrs.damper || me.damper;
+ delete attrs.easing;
+ delete attrs.damper;
+ anim = Ext.create('Ext.fx.Anim', {
+ target: target,
+ easing: easing,
+ damper: damper,
+ duration: anim.duration,
+ paused: true,
+ to: attrs
+ });
+ anims.push(anim);
+ }
+ me.animations = anims;
+ me.target = anim.target;
+ 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);
+ }, me);
+ }
+ anims[ln - 1].on('afteranimate', function() {
+ this.lastFrame();
+ }, me);
+ }
+ },
+
+ /*
+ * @private
+ * Fires beforeanimate and sets the running flag.
+ */
+ start: function(startTime) {
+ var me = this,
+ delay = me.delay,
+ delayStart = me.delayStart,
+ delayDelta;
+ if (delay) {
+ if (!delayStart) {
+ me.delayStart = startTime;
+ return;
+ }
+ else {
+ delayDelta = startTime - delayStart;
+ if (delayDelta < delay) {
+ return;
+ }
+ else {
+ // Compensate for frame delay;
+ startTime = new Date(delayStart.getTime() + delay);
+ }
+ }
+ }
+ if (me.fireEvent('beforeanimate', me) !== false) {
+ me.startTime = startTime;
+ me.running = true;
+ me.animations[me.keyframeStep].paused = false;
+ }
+ },
+
+ /*
+ * @private
+ * Perform lastFrame cleanup and handle iterations
+ * @returns a hash of the new attributes.
+ */
+ lastFrame: function() {
+ var me = this,
+ iter = me.iterations,
+ iterCount = me.currentIteration;
+
+ iterCount++;
+ if (iterCount < iter) {
+ me.startTime = new Date();
+ me.currentIteration = iterCount;
+ me.keyframeStep = 0;
+ me.applyAnimator(me.target);
+ me.animations[me.keyframeStep].paused = false;
+ }
+ else {
+ me.currentIteration = 0;
+ me.end();
+ }
+ },
+
+ /*
+ * Fire afteranimate event and end the animation. Usually called automatically when the
+ * animation reaches its final frame, but can also be called manually to pre-emptively
+ * stop and destroy the running animation.
+ */
+ end: function() {
+ var me = this;
+ me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
+ }
+});
\ No newline at end of file