/** * 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 * * @example * var myComponent = Ext.create('Ext.Component', { * renderTo: document.body, * width: 200, * height: 200, * style: 'border: 1px solid red;' * }); * * Ext.create('Ext.fx.Anim', { * target: myComponent, * duration: 1000, * from: { * width: 400 //starting width 400 * }, * to: { * width: 300, //end width 300 * height: 300 // end height 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 */ /** * @property {Boolean} isAnimation * `true` in this class to identify an object as an instantiated Anim, or subclass thereof. */ isAnimation: true, /** * @cfg {Function/String} callback * A function to be run after the animation has completed. * @controllable */ /** * @cfg {Object} scope * The scope that the {@link #callback} function will be called with */ /** * @cfg {Boolean} remove * `true` to remove the target when the animation is complete, using the appropriate removal * method for the target. For example, a component will be destroyed, elements will be removed. */ /** * @cfg {Number} duration * Time in milliseconds for a single animation to last. 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. */ delay: 0, /** * @private * Tracks 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. */ 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][0] * specification. 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. * * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag */ 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 un-paused later; otherwise call {@link #end}. * @property paused * @type Boolean */ paused: false, /** * @cfg {Number} iterations * Number of times to execute the animation. */ iterations: 1, /** * @cfg {Boolean} autoEnd * `true` to immediately force this animation to its final state. This can be useful * in cases where you want the final effect of an animation, but need to the actual * animation dynamically. Also see the {@link #jumpToEnd} method. */ autoEnd: false, /** * @cfg {Boolean} alternate * Used in conjunction with iterations to reverse the animation each time an iteration completes. */ alternate: false, /** * Current iteration the animation is running. * @property currentIteration * @type Number */ 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 (required) * An object containing property/value pairs for the end of the animation. For example: * * to: { * opacity: 1, // Opaque * color: '#00ff00', // Green * left: 500 * } * */ /** * @private */ frameCount: 0, /** * @event beforeanimate * Fires before the animation starts. A handler can return false to cancel the animation. * @param {Ext.fx.Anim} this */ /** * @event afteranimate * Fires when the animation is complete. * @param {Ext.fx.Anim} this * @param {Date} startTime */ /** * @event lastframe * Fires when the animation's last frame has been set. * @param {Ext.fx.Anim} this * @param {Date} startTime */ /** * @private */ constructor: function(config) { var me = this, curve; config = config || {}; // If keyframes are passed, they really want an Animator instead. if (config.keyframes) { return new Ext.fx.Animator(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) { curve = me.easingFn; me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]); } } me.id = Ext.id(null, 'ext-anim-'); me.mixins.observable.constructor.call(me); Ext.fx.Manager.addAnim(me); if (config.autoEnd) { me.running = true; me.jumpToEnd(); } }, /** * @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; me.frameCount = 0; } }, /** * Immediately force this animation to its final state. */ jumpToEnd: function(){ var me = this; if (!me.endWasCalled) { if (!me.currentAttrs) { me.initAttrs(); } Ext.fx.Manager.jumpToEnd(me); me.end(); } }, /** * @private * Calculate attribute value at the passed timestamp. * @return 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); } } me.frameCount++; return ret; }, /** * @private * Perform lastFrame cleanup and handle iterations * @return 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); } }, endWasCalled: 0, /** * Fire afteranimate event and end the animation. Usually called automatically when the * animation reaches its final frame, but can also be called manually to preemptively * stop and destroy the running animation. */ end: function(suppressEvent) { var me = this; if (me.endWasCalled++) { return; } me.startTime = 0; me.paused = false; me.running = false; Ext.fx.Manager.removeAnim(me); if (!suppressEvent) { me.fireEvent('afteranimate', me, me.startTime); Ext.callback(me.callback, me.scope, [me, me.startTime]); } if (me.remove) { me.target.destroy(); } }, isReady: function() { return this.paused === false && this.running === false && this.iterations > 0; }, isRunning: function() { return this.paused === false && this.running === true && this.isAnimator !== true; }}); /** * @member Ext * @property {Boolean} enableFx * True if the {@link Ext.fx.Anim} Class is available. */Ext.enableFx = true; // Indicate that Fx is available. Class might not be available immediately.