]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/fx/Manager.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / fx / Manager.js
CommitLineData
6527f429
DM
1/**\r
2 * @class Ext.fx.Manager\r
3 * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.\r
4 * @private\r
5 * @singleton\r
6 */\r
7\r
8Ext.define('Ext.fx.Manager', {\r
9\r
10 /* Begin Definitions */\r
11\r
12 singleton: true,\r
13\r
14 requires: [\r
15 'Ext.util.MixedCollection',\r
16 'Ext.util.TaskRunner',\r
17 'Ext.fx.target.Element',\r
18 'Ext.fx.target.ElementCSS',\r
19 'Ext.fx.target.CompositeElement',\r
20 'Ext.fx.target.CompositeElementCSS',\r
21 'Ext.fx.target.Sprite',\r
22 'Ext.fx.target.CompositeSprite',\r
23 'Ext.fx.target.Component'\r
24 ],\r
25\r
26 mixins: {\r
27 queue: 'Ext.fx.Queue'\r
28 },\r
29\r
30 /* End Definitions */\r
31\r
32 /**\r
33 * @private\r
34 */\r
35 constructor: function() {\r
36 var me = this;\r
37 me.items = new Ext.util.MixedCollection();\r
38 me.targetArr = {};\r
39 me.mixins.queue.constructor.call(me);\r
40 \r
41 // Do not use fireIdleEvent: false. Each tick of the TaskRunner needs to fire the idleEvent\r
42 // in case an animation callback/listener adds a listener.\r
43 me.taskRunner = new Ext.util.TaskRunner();\r
44 },\r
45\r
46 /**\r
47 * @cfg {Number} interval Default interval in miliseconds to calculate each frame. Defaults to 16ms (~60fps)\r
48 */\r
49 interval: 16,\r
50\r
51 /**\r
52 * @cfg {Boolean} forceJS Force the use of JavaScript-based animation instead of CSS3 animation, even when CSS3\r
53 * animation is supported by the browser. This defaults to true currently, as CSS3 animation support is still\r
54 * considered experimental at this time, and if used should be thouroughly tested across all targeted browsers.\r
55 * @protected\r
56 */\r
57 forceJS: true,\r
58\r
59 /**\r
60 * @private\r
61 * Target Factory\r
62 */\r
63 createTarget: function(target) {\r
64 var me = this,\r
65 useCSS3 = !me.forceJS && Ext.supports.Transitions,\r
66 targetObj;\r
67\r
68 me.useCSS3 = useCSS3;\r
69\r
70 if (target) {\r
71 // dom element, string or fly\r
72 if (target.tagName || Ext.isString(target) || target.isFly) {\r
73 target = Ext.get(target);\r
74 targetObj = new Ext.fx.target['Element' + (useCSS3 ? 'CSS' : '')](target);\r
75 }\r
76 // Element\r
77 else if (target.dom) {\r
78 targetObj = new Ext.fx.target['Element' + (useCSS3 ? 'CSS' : '')](target);\r
79 }\r
80 // Element Composite\r
81 else if (target.isComposite) {\r
82 targetObj = new Ext.fx.target['CompositeElement' + (useCSS3 ? 'CSS' : '')](target);\r
83 }\r
84 // Draw Sprite\r
85 else if (target.isSprite) {\r
86 targetObj = new Ext.fx.target.Sprite(target);\r
87 }\r
88 // Draw Sprite Composite\r
89 else if (target.isCompositeSprite) {\r
90 targetObj = new Ext.fx.target.CompositeSprite(target);\r
91 }\r
92 // Component\r
93 else if (target.isComponent) {\r
94 targetObj = new Ext.fx.target.Component(target);\r
95 }\r
96 else if (target.isAnimTarget) {\r
97 return target;\r
98 }\r
99 else {\r
100 return null;\r
101 }\r
102 me.targets.add(targetObj);\r
103 return targetObj;\r
104 }\r
105 else {\r
106 return null;\r
107 }\r
108 },\r
109\r
110 /**\r
111 * Add an Anim to the manager. This is done automatically when an Anim instance is created.\r
112 * @param {Ext.fx.Anim} anim\r
113 */\r
114 addAnim: function(anim) {\r
115 var me = this,\r
116 items = me.items,\r
117 task = me.task;\r
118\r
119 // Make sure we use the anim's id, not the anim target's id here. The anim id will be unique on\r
120 // each call to addAnim. `anim.target` is the DOM element being targeted, and since multiple animations\r
121 // can target a single DOM node concurrently, the target id cannot be assumned to be unique.\r
122 items.add(anim.id, anim);\r
123 //Ext.log('+ added anim ', anim.id, ', target: ', anim.target.getId(), ', duration: ', anim.duration);\r
124\r
125 // Start the timer if not already running\r
126 if (!task && items.length) {\r
127 task = me.task = {\r
128 run: me.runner,\r
129 interval: me.interval,\r
130 scope: me\r
131 };\r
132 //Ext.log('--->> Starting task');\r
133 me.taskRunner.start(task);\r
134 }\r
135 },\r
136\r
137 /**\r
138 * Remove an Anim from the manager. This is done automatically when an Anim ends.\r
139 * @param {Ext.fx.Anim} anim\r
140 */\r
141 removeAnim: function(anim) {\r
142 var me = this,\r
143 items = me.items,\r
144 task = me.task;\r
145 \r
146 items.removeAtKey(anim.id);\r
147 //Ext.log(' X removed anim ', anim.id, ', target: ', anim.target.getId(), ', frames: ', anim.frameCount, ', item count: ', items.length);\r
148 \r
149 // Stop the timer if there are no more managed Anims\r
150 if (task && !items.length) {\r
151 //Ext.log('[]--- Stopping task');\r
152 me.taskRunner.stop(task);\r
153 delete me.task;\r
154 }\r
155 },\r
156\r
157 /**\r
158 * @private\r
159 * Runner function being called each frame\r
160 */\r
161 runner: function() {\r
162 var me = this,\r
163 items = me.items.getRange(),\r
164 i = 0,\r
165 len = items.length,\r
166 anim;\r
167\r
168 //Ext.log(' executing anim runner task with ', len, ' items');\r
169 me.targetArr = {};\r
170\r
171 // Single timestamp for all animations this interval\r
172 me.timestamp = new Date();\r
173 \r
174 // Loop to start any new animations first before looping to\r
175 // execute running animations (which will also include all animations\r
176 // started in this loop). This is a subtle difference from simply\r
177 // iterating in one loop and starting then running each animation,\r
178 // but separating the loops is necessary to ensure that all new animations\r
179 // actually kick off prior to existing ones regardless of array order.\r
180 // Otherwise in edge cases when there is excess latency in overall\r
181 // performance, allowing existing animations to run before new ones can\r
182 // lead to dropped frames and subtle race conditions when they are\r
183 // interdependent, which is often the case with certain Element fx.\r
184 for (; i < len; i++) {\r
185 anim = items[i];\r
186 \r
187 if (anim.isReady()) {\r
188 //Ext.log(' starting anim ', anim.id, ', target: ', anim.target.id);\r
189 me.startAnim(anim);\r
190 }\r
191 }\r
192 \r
193 for (i = 0; i < len; i++) {\r
194 anim = items[i];\r
195 \r
196 if (anim.isRunning()) {\r
197 //Ext.log(' running anim ', anim.target.id);\r
198 me.runAnim(anim);\r
199 }\r
200 //<debug>\r
201 //else if (!me.useCSS3) {\r
202 // When using CSS3 transitions the animations get paused since they are not\r
203 // needed once the transition is handed over to the browser, so we can\r
204 // ignore this case. However if we are doing JS animations and something is\r
205 // paused here it's possibly unintentional.\r
206 //Ext.log(' (i) anim ', anim.id, ' is active but not running...');\r
207 //}\r
208 //</debug>\r
209 }\r
210\r
211 // Apply all the pending changes to their targets\r
212 me.applyPendingAttrs();\r
213 \r
214 // Avoid retaining target references after we are finished with anims\r
215 me.targetArr = null;\r
216 },\r
217\r
218 /**\r
219 * @private\r
220 * Start the individual animation (initialization)\r
221 */\r
222 startAnim: function(anim) {\r
223 anim.start(this.timestamp);\r
224 },\r
225\r
226 /**\r
227 * @private\r
228 * Run the individual animation for this frame\r
229 */\r
230 runAnim: function(anim, forceEnd) {\r
231 if (!anim) {\r
232 return;\r
233 }\r
234 var me = this,\r
235 useCSS3 = me.useCSS3 && anim.target.type === 'element',\r
236 elapsedTime = me.timestamp - anim.startTime,\r
237 lastFrame = (elapsedTime >= anim.duration),\r
238 target, o;\r
239 \r
240 if (forceEnd) {\r
241 elapsedTime = anim.duration;\r
242 lastFrame = true;\r
243 }\r
244\r
245 target = this.collectTargetData(anim, elapsedTime, useCSS3, lastFrame);\r
246 \r
247 // For CSS3 animation, we need to immediately set the first frame's attributes without any transition\r
248 // to get a good initial state, then add the transition properties and set the final attributes.\r
249 if (useCSS3) {\r
250 //Ext.log(' (i) using CSS3 transitions');\r
251 \r
252 // Flush the collected attributes, without transition\r
253 anim.target.setAttr(target.anims[anim.id].attributes, true);\r
254\r
255 // Add the end frame data\r
256 me.collectTargetData(anim, anim.duration, useCSS3, lastFrame);\r
257\r
258 // Pause the animation so runAnim doesn't keep getting called\r
259 anim.paused = true;\r
260\r
261 target = anim.target.target;\r
262 // We only want to attach an event on the last element in a composite\r
263 if (anim.target.isComposite) {\r
264 target = anim.target.target.last();\r
265 }\r
266\r
267 // Listen for the transitionend event\r
268 o = {};\r
269 o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;\r
270 o.scope = anim;\r
271 o.single = true;\r
272 target.on(o);\r
273 }\r
274 return target;\r
275 },\r
276 \r
277 jumpToEnd: function(anim) {\r
278 var me = this,\r
279 target, clear;\r
280\r
281 // We may not be in the middle of a tick, where targetAttr is cleared,\r
282 // so if we don't have it, poke it in here while we jump to the end state\r
283 if (!me.targetArr) {\r
284 me.targetArr = {};\r
285 clear = true;\r
286 }\r
287\r
288 target = me.runAnim(anim, true);\r
289 me.applyAnimAttrs(target, target.anims[anim.id]);\r
290 if (clear) {\r
291 me.targetArr = null;\r
292 }\r
293 },\r
294\r
295 /**\r
296 * @private\r
297 * Collect target attributes for the given Anim object at the given timestamp\r
298 * @param {Ext.fx.Anim} anim The Anim instance\r
299 * @param {Number} timestamp Time after the anim's start time\r
300 * @param {Boolean} [useCSS3=false] True if using CSS3-based animation, else false\r
301 * @param {Boolean} [isLastFrame=false] True if this is the last frame of animation to be run, else false\r
302 * @return {Object} The animation target wrapper object containing the passed animation along with the\r
303 * new attributes to set on the target's element in the next animation frame.\r
304 */\r
305 collectTargetData: function(anim, elapsedTime, useCSS3, isLastFrame) {\r
306 var targetId = anim.target.getId(),\r
307 target = this.targetArr[targetId];\r
308 \r
309 if (!target) {\r
310 // Create a thin wrapper around the target so that we can create a link between the\r
311 // target element and its associated animations. This is important later when applying\r
312 // attributes to the target so that each animation can be independently run with its own\r
313 // duration and stopped at any point without affecting other animations for the same target.\r
314 target = this.targetArr[targetId] = {\r
315 id: targetId,\r
316 el: anim.target,\r
317 anims: {}\r
318 };\r
319 }\r
320\r
321 // This is a wrapper for the animation so that we can also save state along with it,\r
322 // including the current elapsed time and lastFrame status. Even though this method only\r
323 // adds a single anim object per call, each target element could have multiple animations\r
324 // associated with it, which is why the anim is added to the target's `anims` hash by id.\r
325 target.anims[anim.id] = {\r
326 id: anim.id,\r
327 anim: anim,\r
328 elapsed: elapsedTime,\r
329 isLastFrame: isLastFrame,\r
330 // This is the object that gets applied to the target element below in applyPendingAttrs():\r
331 attributes: [{\r
332 duration: anim.duration,\r
333 easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,\r
334 // This is where the magic happens. The anim calculates what its new attributes should\r
335 // be based on the current frame and returns those as a hash of values.\r
336 attrs: anim.runAnim(elapsedTime)\r
337 }]\r
338 };\r
339 \r
340 return target;\r
341 },\r
342 \r
343 // Duplicating this code for performance reasons. We only want to apply the anims\r
344 // to a single animation because we're hitting the end. It may be out of sequence from\r
345 // the runner timer.\r
346 applyAnimAttrs: function(target, animWrap) {\r
347 var anim = animWrap.anim;\r
348 if (animWrap.attributes && anim.isRunning()) {\r
349 target.el.setAttr(animWrap.attributes, false, animWrap.isLastFrame);\r
350 \r
351 // If this particular anim is at the last frame end it\r
352 if (animWrap.isLastFrame) {\r
353 anim.lastFrame();\r
354 }\r
355 }\r
356 },\r
357 \r
358 /**\r
359 * @private\r
360 * Apply all pending attribute changes to their targets\r
361 */\r
362 applyPendingAttrs: function() {\r
363 var targetArr = this.targetArr,\r
364 target, targetId, animWrap, anim, animId;\r
365 \r
366 // Loop through each target\r
367 for (targetId in targetArr) {\r
368 if (targetArr.hasOwnProperty(targetId)) {\r
369 target = targetArr[targetId];\r
370 \r
371 // Each target could have multiple associated animations, so iterate those\r
372 for (animId in target.anims) {\r
373 if (target.anims.hasOwnProperty(animId)) {\r
374 animWrap = target.anims[animId];\r
375 anim = animWrap.anim;\r
376 \r
377 // If the animation has valid attributes, set them on the target\r
378 if (animWrap.attributes && anim.isRunning()) {\r
379 //Ext.log(' > applying attributes for anim ', animWrap.id, ', target: ', target.id, ', elapsed: ', animWrap.elapsed);\r
380 target.el.setAttr(animWrap.attributes, false, animWrap.isLastFrame);\r
381 \r
382 // If this particular anim is at the last frame end it\r
383 if (animWrap.isLastFrame) {\r
384 //Ext.log(' running last frame for ', animWrap.id, ', target: ', targetId);\r
385 anim.lastFrame();\r
386 }\r
387 }\r
388 }\r
389 }\r
390 }\r
391 }\r
392 }\r
393});\r