linuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacos
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
374 lines
14 KiB
374 lines
14 KiB
/** |
|
* @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.util.TaskRunner', |
|
'Ext.fx.target.Element', |
|
'Ext.fx.target.ElementCSS', |
|
'Ext.fx.target.CompositeElement', |
|
'Ext.fx.target.CompositeElementCSS', |
|
'Ext.fx.target.Sprite', |
|
'Ext.fx.target.CompositeSprite', |
|
'Ext.fx.target.Component' |
|
], |
|
|
|
mixins: { |
|
queue: 'Ext.fx.Queue' |
|
}, |
|
|
|
/* End Definitions */ |
|
|
|
/** |
|
* @private |
|
*/ |
|
constructor: function() { |
|
var me = this; |
|
me.items = new Ext.util.MixedCollection(); |
|
me.targetArr = {}; |
|
me.mixins.queue.constructor.call(me); |
|
|
|
// Do not use fireIdleEvent: false. Each tick of the TaskRunner needs to fire the idleEvent |
|
// in case an animation callback/listener adds a listener. |
|
me.taskRunner = new Ext.util.TaskRunner(); |
|
}, |
|
|
|
/** |
|
* @cfg {Number} interval Default interval in miliseconds to calculate each frame. Defaults to 16ms (~60fps) |
|
*/ |
|
interval: 16, |
|
|
|
/** |
|
* @cfg {Boolean} forceJS Force the use of JavaScript-based animation instead of CSS3 animation, even when CSS3 |
|
* animation is supported by the browser. This defaults to true currently, as CSS3 animation support is still |
|
* considered experimental at this time, and if used should be thouroughly tested across all targeted browsers. |
|
* @protected |
|
*/ |
|
forceJS: true, |
|
|
|
// @private Target factory |
|
createTarget: function(target) { |
|
var me = this, |
|
useCSS3 = !me.forceJS && Ext.supports.Transitions, |
|
targetObj; |
|
|
|
me.useCSS3 = useCSS3; |
|
|
|
if (target) { |
|
// dom element, string or fly |
|
if (target.tagName || Ext.isString(target) || target.isFly) { |
|
target = Ext.get(target); |
|
targetObj = new Ext.fx.target['Element' + (useCSS3 ? 'CSS' : '')](target); |
|
} |
|
// Element |
|
else if (target.dom) { |
|
targetObj = new Ext.fx.target['Element' + (useCSS3 ? 'CSS' : '')](target); |
|
} |
|
// Element Composite |
|
else if (target.isComposite) { |
|
targetObj = new Ext.fx.target['CompositeElement' + (useCSS3 ? 'CSS' : '')](target); |
|
} |
|
// Draw Sprite |
|
else if (target.isSprite) { |
|
targetObj = new Ext.fx.target.Sprite(target); |
|
} |
|
// Draw Sprite Composite |
|
else if (target.isCompositeSprite) { |
|
targetObj = new Ext.fx.target.CompositeSprite(target); |
|
} |
|
// Component |
|
else if (target.isComponent) { |
|
targetObj = new 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 me = this, |
|
items = me.items, |
|
task = me.task; |
|
|
|
// Make sure we use the anim's id, not the anim target's id here. The anim id will be unique on |
|
// each call to addAnim. `anim.target` is the DOM element being targeted, and since multiple animations |
|
// can target a single DOM node concurrently, the target id cannot be assumned to be unique. |
|
items.add(anim.id, anim); |
|
//Ext.log('+ added anim ', anim.id, ', target: ', anim.target.getId(), ', duration: ', anim.duration); |
|
|
|
// Start the timer if not already running |
|
if (!task && items.length) { |
|
task = me.task = { |
|
run: me.runner, |
|
interval: me.interval, |
|
scope: me |
|
}; |
|
//Ext.log('--->> Starting task'); |
|
me.taskRunner.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 me = this, |
|
items = me.items, |
|
task = me.task; |
|
|
|
items.removeAtKey(anim.id); |
|
//Ext.log(' X removed anim ', anim.id, ', target: ', anim.target.getId(), ', frames: ', anim.frameCount, ', item count: ', items.length); |
|
|
|
// Stop the timer if there are no more managed Anims |
|
if (task && !items.length) { |
|
//Ext.log('[]--- Stopping task'); |
|
me.taskRunner.stop(task); |
|
delete me.task; |
|
} |
|
}, |
|
|
|
/** |
|
* @private |
|
* Runner function being called each frame |
|
*/ |
|
runner: function() { |
|
var me = this, |
|
items = me.items.getRange(), |
|
i = 0, |
|
len = items.length, |
|
anim; |
|
|
|
//Ext.log(' executing anim runner task with ', len, ' items'); |
|
me.targetArr = {}; |
|
|
|
// Single timestamp for all animations this interval |
|
me.timestamp = new Date(); |
|
|
|
// Loop to start any new animations first before looping to |
|
// execute running animations (which will also include all animations |
|
// started in this loop). This is a subtle difference from simply |
|
// iterating in one loop and starting then running each animation, |
|
// but separating the loops is necessary to ensure that all new animations |
|
// actually kick off prior to existing ones regardless of array order. |
|
// Otherwise in edge cases when there is excess latency in overall |
|
// performance, allowing existing animations to run before new ones can |
|
// lead to dropped frames and subtle race conditions when they are |
|
// interdependent, which is often the case with certain Element fx. |
|
for (; i < len; i++) { |
|
anim = items[i]; |
|
|
|
if (anim.isReady()) { |
|
//Ext.log(' starting anim ', anim.id, ', target: ', anim.target.id); |
|
me.startAnim(anim); |
|
} |
|
} |
|
|
|
for (i = 0; i < len; i++) { |
|
anim = items[i]; |
|
|
|
if (anim.isRunning()) { |
|
//Ext.log(' running anim ', anim.target.id); |
|
me.runAnim(anim); |
|
} |
|
//<debug> |
|
//else if (!me.useCSS3) { |
|
// When using CSS3 transitions the animations get paused since they are not |
|
// needed once the transition is handed over to the browser, so we can |
|
// ignore this case. However if we are doing JS animations and something is |
|
// paused here it's possibly unintentional. |
|
//Ext.log(' (i) anim ', anim.id, ' is active but not running...'); |
|
//} |
|
//</debug> |
|
} |
|
|
|
// 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, forceEnd) { |
|
if (!anim) { |
|
return; |
|
} |
|
var me = this, |
|
useCSS3 = me.useCSS3 && anim.target.type === 'element', |
|
elapsedTime = me.timestamp - anim.startTime, |
|
lastFrame = (elapsedTime >= anim.duration), |
|
target, o; |
|
|
|
if (forceEnd) { |
|
elapsedTime = anim.duration; |
|
lastFrame = true; |
|
} |
|
|
|
target = this.collectTargetData(anim, elapsedTime, useCSS3, lastFrame); |
|
|
|
// 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) { |
|
//Ext.log(' (i) using CSS3 transitions'); |
|
|
|
// Flush the collected attributes, without transition |
|
anim.target.setAttr(target.anims[anim.id].attributes, true); |
|
|
|
// Add the end frame data |
|
me.collectTargetData(anim, anim.duration, useCSS3, lastFrame); |
|
|
|
// 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); |
|
} |
|
return target; |
|
}, |
|
|
|
jumpToEnd: function(anim) { |
|
var target = this.runAnim(anim, true); |
|
this.applyAnimAttrs(target, target.anims[anim.id]); |
|
}, |
|
|
|
/** |
|
* @private |
|
* 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 |
|
* @param {Boolean} [useCSS3=false] True if using CSS3-based animation, else false |
|
* @param {Boolean} [isLastFrame=false] True if this is the last frame of animation to be run, else false |
|
* @return {Object} The animation target wrapper object containing the passed animation along with the |
|
* new attributes to set on the target's element in the next animation frame. |
|
*/ |
|
collectTargetData: function(anim, elapsedTime, useCSS3, isLastFrame) { |
|
var targetId = anim.target.getId(), |
|
target = this.targetArr[targetId]; |
|
|
|
if (!target) { |
|
// Create a thin wrapper around the target so that we can create a link between the |
|
// target element and its associated animations. This is important later when applying |
|
// attributes to the target so that each animation can be independently run with its own |
|
// duration and stopped at any point without affecting other animations for the same target. |
|
target = this.targetArr[targetId] = { |
|
id: targetId, |
|
el: anim.target, |
|
anims: {} |
|
}; |
|
} |
|
|
|
// This is a wrapper for the animation so that we can also save state along with it, |
|
// including the current elapsed time and lastFrame status. Even though this method only |
|
// adds a single anim object per call, each target element could have multiple animations |
|
// associated with it, which is why the anim is added to the target's `anims` hash by id. |
|
target.anims[anim.id] = { |
|
id: anim.id, |
|
anim: anim, |
|
elapsed: elapsedTime, |
|
isLastFrame: isLastFrame, |
|
// This is the object that gets applied to the target element below in applyPendingAttrs(): |
|
attributes: [{ |
|
duration: anim.duration, |
|
easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing, |
|
// This is where the magic happens. The anim calculates what its new attributes should |
|
// be based on the current frame and returns those as a hash of values. |
|
attrs: anim.runAnim(elapsedTime) |
|
}] |
|
}; |
|
|
|
return target; |
|
}, |
|
|
|
// Duplicating this code for performance reasons. We only want to apply the anims |
|
// to a single animation because we're hitting the end. It may be out of sequence from |
|
// the runner timer. |
|
applyAnimAttrs: function(target, animWrap) { |
|
var anim = animWrap.anim; |
|
if (animWrap.attributes && anim.isRunning()) { |
|
target.el.setAttr(animWrap.attributes, false, animWrap.isLastFrame); |
|
|
|
// If this particular anim is at the last frame end it |
|
if (animWrap.isLastFrame) { |
|
anim.lastFrame(); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* @private |
|
* Apply all pending attribute changes to their targets |
|
*/ |
|
applyPendingAttrs: function() { |
|
var targetArr = this.targetArr, |
|
target, targetId, animWrap, anim, animId; |
|
|
|
// Loop through each target |
|
for (targetId in targetArr) { |
|
if (targetArr.hasOwnProperty(targetId)) { |
|
target = targetArr[targetId]; |
|
|
|
// Each target could have multiple associated animations, so iterate those |
|
for (animId in target.anims) { |
|
if (target.anims.hasOwnProperty(animId)) { |
|
animWrap = target.anims[animId]; |
|
anim = animWrap.anim; |
|
|
|
// If the animation has valid attributes, set them on the target |
|
if (animWrap.attributes && anim.isRunning()) { |
|
//Ext.log(' > applying attributes for anim ', animWrap.id, ', target: ', target.id, ', elapsed: ', animWrap.elapsed); |
|
target.el.setAttr(animWrap.attributes, false, animWrap.isLastFrame); |
|
|
|
// If this particular anim is at the last frame end it |
|
if (animWrap.isLastFrame) { |
|
//Ext.log(' running last frame for ', animWrap.id, ', target: ', targetId); |
|
anim.lastFrame(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
});
|
|
|