X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/fx/Anim.js diff --git a/src/fx/Anim.js b/src/fx/Anim.js new file mode 100644 index 00000000..b9b38c70 --- /dev/null +++ b/src/fx/Anim.js @@ -0,0 +1,433 @@ +/** + * @class Ext.fx.Anim + * + * This class manages animation for a specific {@link #target}. The animation allows + * animation of various properties on the target, such as size, position, color and others. + * + * ## Starting Conditions + * The starting conditions for the animation are provided by the {@link #from} configuration. + * Any/all of the properties in the {@link #from} configuration can be specified. If a particular + * property is not defined, the starting value for that property will be read directly from the target. + * + * ## End Conditions + * The ending conditions for the animation are provided by the {@link #to} configuration. These mark + * the final values once the animations has finished. The values in the {@link #from} can mirror + * those in the {@link #to} configuration to provide a starting point. + * + * ## Other Options + * - {@link #duration}: Specifies the time period of the animation. + * - {@link #easing}: Specifies the easing of the animation. + * - {@link #iterations}: Allows the animation to repeat a number of times. + * - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration. + * + * ## Example Code + * + * var myComponent = Ext.create('Ext.Component', { + * renderTo: document.body, + * width: 200, + * height: 200, + * style: 'border: 1px solid red;' + * }); + * + * new Ext.fx.Anim({ + * target: myComponent, + * duration: 1000, + * from: { + * width: 400 //starting width 400 + * }, + * to: { + * width: 300, //end width 300 + * height: 300 // end width 300 + * } + * }); + */ +Ext.define('Ext.fx.Anim', { + + /* Begin Definitions */ + + mixins: { + observable: 'Ext.util.Observable' + }, + + requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'], + + /* End Definitions */ + + isAnimation: true, + /** + * @cfg {Number} duration + * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is + * specified, then each animate will take the same duration for each iteration. + */ + 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', + + /** + * @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
+    }
+}
+ 
+ */ + + /** + * @private + */ + damper: 1, + + /** + * @private + */ + bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, + + /** + * Run the animation from the end to the beginning + * Defaults to false. + * @cfg {Boolean} reverse + */ + reverse: false, + + /** + * 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, + + /** + * Number of times to execute the animation. Defaults to 1. + * @cfg {int} iterations + */ + iterations: 1, + + /** + * Used in conjunction with iterations to reverse the animation each time an iteration completes. + * @cfg {Boolean} alternate + * Defaults to false. + */ + alternate: false, + + /** + * Current iteration the animation is running. + * @property currentIteration + * @type int + */ + currentIteration: 0, + + /** + * Starting time of the animation. + * @property startTime + * @type Date + */ + startTime: 0, + + /** + * Contains a cache of the interpolators to be used. + * @private + * @property propHandlers + * @type Object + */ + + /** + * @cfg {String/Object} target + * The {@link Ext.fx.target.Target} to apply the animation to. This should only be specified when creating an Ext.fx.Anim directly. + * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can + * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object + * automatically. + */ + + /** + * @cfg {Object} from + * An object containing property/value pairs for the beginning of the animation. If not specified, the current state of the + * Ext.fx.target will be used. For example: +

+from : {
+    opacity: 0,       // Transparent
+    color: '#ffffff', // White
+    left: 0
+}
+
+ */ + + /** + * @cfg {Object} to + * An object containing property/value pairs for the end of the animation. For example: +

+ to : {
+     opacity: 1,       // Opaque
+     color: '#00ff00', // Green
+     left: 500
+ }
+ 
+ */ + + // @private + constructor: function(config) { + var me = this; + config = config || {}; + // If keyframes are passed, they really want an Animator instead. + if (config.keyframes) { + return Ext.create('Ext.fx.Animator', config); + } + config = Ext.apply(me, config); + if (me.from === undefined) { + me.from = {}; + } + me.propHandlers = {}; + me.config = config; + me.target = Ext.fx.Manager.createTarget(me.target); + me.easingFn = Ext.fx.Easing[me.easing]; + me.target.dynamic = me.dynamic; + + // If not a pre-defined curve, try a cubic-bezier + if (!me.easingFn) { + me.easingFn = String(me.easing).match(me.bezierRE); + if (me.easingFn && me.easingFn.length == 5) { + var curve = me.easingFn; + me.easingFn = Ext.fx.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]); + } + } + me.id = Ext.id(null, 'ext-anim-'); + Ext.fx.Manager.addAnim(me); + me.addEvents( + /** + * @event beforeanimate + * Fires before the animation starts. A handler can return false to cancel the animation. + * @param {Ext.fx.Anim} this + */ + 'beforeanimate', + /** + * @event afteranimate + * Fires when the animation is complete. + * @param {Ext.fx.Anim} this + * @param {Date} startTime + */ + 'afteranimate', + /** + * @event lastframe + * Fires when the animation's last frame has been set. + * @param {Ext.fx.Anim} this + * @param {Date} startTime + */ + 'lastframe' + ); + me.mixins.observable.constructor.call(me, config); + if (config.callback) { + me.on('afteranimate', config.callback, config.scope); + } + return me; + }, + + /** + * @private + * Helper to the target + */ + setAttr: function(attr, value) { + return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value); + }, + + /* + * @private + * Set up the initial currentAttrs hash. + */ + initAttrs: function() { + var me = this, + from = me.from, + to = me.to, + initialFrom = me.initialFrom || {}, + out = {}, + start, end, propHandler, attr; + + for (attr in to) { + if (to.hasOwnProperty(attr)) { + start = me.target.getAttr(attr, from[attr]); + end = to[attr]; + // Use default (numeric) property handler + if (!Ext.fx.PropertyHandler[attr]) { + if (Ext.isObject(end)) { + propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object; + } else { + propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler; + } + } + // Use custom handler + else { + propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr]; + } + out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr); + } + } + me.currentAttrs = out; + }, + + /* + * @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; + if (!me.paused && !me.currentAttrs) { + me.initAttrs(); + } + me.running = true; + } + }, + + /* + * @private + * Calculate attribute value at the passed timestamp. + * @returns a hash of the new attributes. + */ + runAnim: function(elapsedTime) { + var me = this, + attrs = me.currentAttrs, + duration = me.duration, + easingFn = me.easingFn, + propHandlers = me.propHandlers, + ret = {}, + easing, values, attr, lastFrame; + + if (elapsedTime >= duration) { + elapsedTime = duration; + lastFrame = true; + } + if (me.reverse) { + elapsedTime = duration - elapsedTime; + } + + for (attr in attrs) { + if (attrs.hasOwnProperty(attr)) { + values = attrs[attr]; + easing = lastFrame ? 1 : easingFn(elapsedTime / duration); + ret[attr] = propHandlers[attr].set(values, easing); + } + } + return ret; + }, + + /* + * @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) { + if (me.alternate) { + me.reverse = !me.reverse; + } + me.startTime = new Date(); + me.currentIteration = iterCount; + // Turn off paused for CSS3 Transitions + me.paused = false; + } + else { + me.currentIteration = 0; + me.end(); + me.fireEvent('lastframe', me, me.startTime); + } + }, + + /* + * 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.startTime = 0; + me.paused = false; + me.running = false; + Ext.fx.Manager.removeAnim(me); + me.fireEvent('afteranimate', me, me.startTime); + } +}); +// Set flag to indicate that Fx is available. Class might not be available immediately. +Ext.enableFx = true;