/** * @class Ext.fx.Manager * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis. * @private * @singleton */ Ext.define('Ext.fx.Manager', { /* Begin Definitions */ singleton: true, requires: ['Ext.util.MixedCollection', 'Ext.fx.target.Element', 'Ext.fx.target.CompositeElement', 'Ext.fx.target.Sprite', 'Ext.fx.target.CompositeSprite', 'Ext.fx.target.Component'], mixins: { queue: 'Ext.fx.Queue' }, /* End Definitions */ constructor: function() { this.items = Ext.create('Ext.util.MixedCollection'); this.mixins.queue.constructor.call(this); // this.requestAnimFrame = (function() { // var raf = window.requestAnimationFrame || // window.webkitRequestAnimationFrame || // window.mozRequestAnimationFrame || // window.oRequestAnimationFrame || // window.msRequestAnimationFrame; // if (raf) { // return function(callback, element) { // raf(callback); // }; // } // else { // return function(callback, element) { // window.setTimeout(callback, Ext.fx.Manager.interval); // }; // } // })(); }, /** * @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, // @private Target factory createTarget: function(target) { var me = this, useCSS3 = !me.forceJS && Ext.supports.Transitions, targetObj; me.useCSS3 = useCSS3; // dom id if (Ext.isString(target)) { target = Ext.get(target); } // dom element if (target && target.tagName) { target = Ext.get(target); targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target); me.targets.add(targetObj); return targetObj; } if (Ext.isObject(target)) { // Element if (target.dom) { targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target); } // Element Composite else if (target.isComposite) { targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target); } // Draw Sprite else if (target.isSprite) { targetObj = Ext.create('Ext.fx.target.Sprite', target); } // Draw Sprite Composite else if (target.isCompositeSprite) { targetObj = Ext.create('Ext.fx.target.CompositeSprite', target); } // Component else if (target.isComponent) { targetObj = Ext.create('Ext.fx.target.Component', target); } else if (target.isAnimTarget) { return target; } else { return null; } me.targets.add(targetObj); return targetObj; } 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; // var me = this, // items = me.items, // cb = function() { // if (items.length) { // me.task = true; // me.runner(); // me.requestAnimFrame(cb); // } // else { // me.task = false; // } // }; 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.TaskManager.start(task); } // //Start the timer if not already running // if (!me.task && items.length) { // me.requestAnimFrame(cb); // } }, /** * Remove an Anim from the manager. This is done automatically when an Anim ends. * @param {Ext.fx.Anim} anim */ removeAnim: function(anim) { // this.items.remove(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.TaskManager.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 && o.isAnimator !== true; }, /** * @private * Runner function being called each frame */ runner: function() { var me = this, items = me.items; me.targetData = {}; me.targetArr = {}; // Single timestamp for all animations this interval me.timestamp = new Date(); // Start any items not current running items.filterBy(me.startingFilter).each(me.startAnim, me); // Build the new attributes to be applied for all targets in this frame items.filterBy(me.runningFilter).each(me.runAnim, me); // Apply all the pending changes to their targets me.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) { if (!anim) { return; } var me = this, targetId = anim.target.getId(), useCSS3 = me.useCSS3 && anim.target.type == 'element', elapsedTime = me.timestamp - anim.startTime, target, o; this.collectTargetData(anim, elapsedTime, 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(me.targetData[targetId], true); // Add the end frame data me.targetData[targetId] = []; me.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 (elapsedTime >= anim.duration) { me.applyPendingAttrs(true); delete me.targetData[targetId]; delete me.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, elapsedTime, 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(elapsedTime)); targetData.push(data); }, /** * @private * Apply all pending attribute changes to their targets */ applyPendingAttrs: function(isLastFrame) { var targetData = this.targetData, targetArr = this.targetArr, targetId; for (targetId in targetData) { if (targetData.hasOwnProperty(targetId)) { targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame); } } } });