X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/fx/Animator.js diff --git a/src/fx/Animator.js b/src/fx/Animator.js new file mode 100644 index 00000000..cec7577d --- /dev/null +++ b/src/fx/Animator.js @@ -0,0 +1,396 @@ +/** + * @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%'.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) { + 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