/** * @class Ext.fx.Manager * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis. * @singleton */ Ext.define('Ext.fx.Manager', { /* Begin Definitions */ singleton: true, requires: ['Ext.util.MixedCollection'], /* End Definitions */ constructor: function() { this.items = new Ext.util.MixedCollection(); },
/** * @cfg {Number} interval Default interval in miliseconds to calculate each frame. Defaults to 16ms (~60fps) */ interval: 16,
/** * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available */ forceJS: true, createTarget: function(target) { var useCSS3 = !this.forceJS && Ext.supports.Transitions; this.useCSS3 = useCSS3; // dom id if (typeof target == 'string') { target = Ext.get(target); } // dom element else if (target.tagName) { target = Ext.get(target); } if (Ext.isObject(target)) { // Element if (target.dom) { return Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target); } // Element Composite else if (target.isComposite) { return Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target); } // Draw Sprite else if (target.isSprite) { return Ext.create('Ext.fx.target.Sprite', target); } // Draw Sprite Composite else if (target.isSpriteGroup) { return Ext.create('Ext.fx.target.SpriteGroup', target); } // Component else if (target.isComponent) { return Ext.create('Ext.fx.target.Component', target); } // Component Compositishtypethingy (When ComponentQuery is complete) //else if (target.isComponent) { // return new Ext.fx.target['Component' + (useCSS3 ? 'CSS' : '')](target); //} // Target else if (target.isAnimTarget) { return target; } else { return null; } } else { return null; } },
/** * Add an Anim to the manager. This is done automatically when an Anim instance is created. * @param {Ext.fx.Anim} anim */ addAnim: function(anim) { var items = this.items, task = this.task; items.add(anim); // Start the timer if not already running if (!task && items.length) { task = this.task = { run: this.runner, interval: this.interval, scope: this }; Ext.TaskMgr.start(task); } },
/** * Remove an Anim from the manager. This is done automatically when an Anim ends. * @param {Ext.fx.Anim} anim */ removeAnim: function(anim) { var items = this.items, task = this.task; items.remove(anim); // Stop the timer if there are no more managed Anims if (task && !items.length) { Ext.TaskMgr.stop(task); delete this.task; } }, /** * @private * Filter function to determine which animations need to be started */ startingFilter: function(o) { return o.paused === false && o.running === false && o.iterations > 0; }, /** * @private * Filter function to determine which animations are still running */ runningFilter: function(o) { return o.paused === false && o.running === true; }, /** * @private * Runner function being called each frame */ runner: function() { var items = this.items; this.targetData = {}; this.targetArr = {}; // Single timestamp for all animations this interval this.timestamp = new Date(); // Start any items not current running items.filterBy(this.startingFilter).each(this.startAnim, this); // Build the new attributes to be applied for all targets in this frame items.filterBy(this.runningFilter).each(this.runAnim, this); // Apply all the pending changes to their targets this.applyPendingAttrs(); }, /** * @private * Start the individual animation (initialization) */ startAnim: function(anim) { anim.start(this.timestamp); }, /** * @private * Run the individual animation for this frame */ runAnim: function(anim) { var targetId = anim.target.getId(), useCSS3 = this.useCSS3 && anim.target.type == 'element', timestamp = this.timestamp - anim.startTime, target, o; this.collectTargetData(anim, timestamp, useCSS3); // For CSS3 animation, we need to immediately set the first frame's attributes without any transition // to get a good initial state, then add the transition properties and set the final attributes. if (useCSS3) { // Flush the collected attributes, without transition anim.target.setAttr(this.targetData[targetId], true); // Add the end frame data this.targetData[targetId] = []; this.collectTargetData(anim, anim.duration, useCSS3); // Pause the animation so runAnim doesn't keep getting called anim.paused = true; target = anim.target.target; // We only want to attach an event on the last element in a composite if (anim.target.isComposite) { target = anim.target.target.last(); } // Listen for the transitionend event o = {}; o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame; o.scope = anim; o.single = true; target.on(o); } // For JS animation, trigger the lastFrame handler if this is the final frame else if (timestamp >= anim.duration) { this.applyPendingAttrs(); delete this.targetData[targetId]; delete this.targetArr[targetId]; anim.lastFrame(); } },
/** * Collect target attributes for the given Anim object at the given timestamp * @param {Ext.fx.Anim} anim The Anim instance * @param {Number} timestamp Time after the anim's start time */ collectTargetData: function(anim, timestamp, useCSS3) { var targetId = anim.target.getId(), targetData = this.targetData[targetId], data; if (!targetData) { targetData = this.targetData[targetId] = []; this.targetArr[targetId] = anim.target; } data = { duration: anim.duration, easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing, attrs: {} }; Ext.apply(data.attrs, anim.runAnim(timestamp)); targetData.push(data); }, /** * @private * Apply all pending attribute changes to their targets */ applyPendingAttrs: function() { var targetData = this.targetData, targetArr = this.targetArr, targetId; for (targetId in targetData) { if (targetData.hasOwnProperty(targetId)) { targetArr[targetId].setAttr(targetData[targetId], false); } } } });