]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/fx/Animator.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / fx / Animator.js
CommitLineData
6527f429
DM
1/**\r
2 * @class Ext.fx.Animator\r
3 *\r
4 * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.\r
5 * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values\r
6 * at various points throughout the animation.\r
7 *\r
8 * ## Using Keyframes\r
9 *\r
10 * The {@link #keyframes} option is the most important part of specifying an animation when using this\r
11 * class. A key frame is a point in a particular animation. We represent this as a percentage of the\r
12 * total animation duration. At each key frame, we can specify the target values at that time. Note that\r
13 * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}\r
14 * event that fires after each key frame is reached.\r
15 *\r
16 * ## Example\r
17 *\r
18 * In the example below, we modify the values of the element at each fifth throughout the animation.\r
19 *\r
20 * @example\r
21 * Ext.create('Ext.fx.Animator', {\r
22 * target: Ext.getBody().createChild({\r
23 * style: {\r
24 * width: '100px',\r
25 * height: '100px',\r
26 * 'background-color': 'red'\r
27 * }\r
28 * }),\r
29 * duration: 10000, // 10 seconds\r
30 * keyframes: {\r
31 * 0: {\r
32 * opacity: 1,\r
33 * backgroundColor: 'FF0000'\r
34 * },\r
35 * 20: {\r
36 * x: 30,\r
37 * opacity: 0.5\r
38 * },\r
39 * 40: {\r
40 * x: 130,\r
41 * backgroundColor: '0000FF'\r
42 * },\r
43 * 60: {\r
44 * y: 80,\r
45 * opacity: 0.3\r
46 * },\r
47 * 80: {\r
48 * width: 200,\r
49 * y: 200\r
50 * },\r
51 * 100: {\r
52 * opacity: 1,\r
53 * backgroundColor: '00FF00'\r
54 * }\r
55 * }\r
56 * });\r
57 */\r
58Ext.define('Ext.fx.Animator', {\r
59\r
60 /* Begin Definitions */\r
61\r
62 mixins: {\r
63 observable: 'Ext.util.Observable'\r
64 },\r
65\r
66 requires: ['Ext.fx.Manager'],\r
67\r
68 /* End Definitions */\r
69\r
70 /**\r
71 * @property {Boolean} isAnimator\r
72 * `true` in this class to identify an object as an instantiated Animator, or subclass thereof.\r
73 */\r
74 isAnimator: true,\r
75\r
76 /**\r
77 * @cfg {Number} duration\r
78 * Time in milliseconds for the animation to last. Defaults to 250.\r
79 */\r
80 duration: 250,\r
81\r
82 /**\r
83 * @cfg {Number} delay\r
84 * Time to delay before starting the animation. Defaults to 0.\r
85 */\r
86 delay: 0,\r
87\r
88 // private used to track a delayed starting time\r
89 delayStart: 0,\r
90\r
91 /**\r
92 * @cfg {Boolean} dynamic\r
93 * 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. Defaults to false.\r
94 */\r
95 dynamic: false,\r
96\r
97 /**\r
98 * @cfg {String} easing\r
99 *\r
100 * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change\r
101 * speed over its duration.\r
102 *\r
103 * - backIn\r
104 * - backOut\r
105 * - bounceIn\r
106 * - bounceOut\r
107 * - ease\r
108 * - easeIn\r
109 * - easeOut\r
110 * - easeInOut\r
111 * - elasticIn\r
112 * - elasticOut\r
113 * - cubic-bezier(x1, y1, x2, y2)\r
114 *\r
115 * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]\r
116 * specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must\r
117 * be in the range [0, 1] or the definition is invalid.\r
118 *\r
119 * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag\r
120 */\r
121 easing: 'ease',\r
122\r
123 /**\r
124 * Flag to determine if the animation has started\r
125 * @property running\r
126 * @type Boolean\r
127 */\r
128 running: false,\r
129\r
130 /**\r
131 * Flag to determine if the animation is paused. Only set this to true if you need to\r
132 * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.\r
133 * @property paused\r
134 * @type Boolean\r
135 */\r
136 paused: false,\r
137\r
138 /**\r
139 * @private\r
140 */\r
141 damper: 1,\r
142\r
143 /**\r
144 * @cfg {Number} iterations\r
145 * Number of times to execute the animation. Defaults to 1.\r
146 */\r
147 iterations: 1,\r
148\r
149 /**\r
150 * Current iteration the animation is running.\r
151 * @property currentIteration\r
152 * @type Number\r
153 */\r
154 currentIteration: 0,\r
155\r
156 /**\r
157 * Current keyframe step of the animation.\r
158 * @property keyframeStep\r
159 * @type Number\r
160 */\r
161 keyframeStep: 0,\r
162\r
163 /**\r
164 * @private\r
165 */\r
166 animKeyFramesRE: /^(from|to|\d+%?)$/,\r
167\r
168 /**\r
169 * @cfg {Ext.fx.target.Target} target\r
170 * The Ext.fx.target to apply the animation to. If not specified during initialization, this can be passed to the applyAnimator\r
171 * method to apply the same animation to many targets.\r
172 */\r
173\r
174 /**\r
175 * @event beforeanimate\r
176 * Fires before the animation starts. A handler can return false to cancel the animation.\r
177 * @param {Ext.fx.Animator} this\r
178 */\r
179\r
180 /**\r
181 * @event keyframe\r
182 * Fires at each keyframe.\r
183 * @param {Ext.fx.Animator} this\r
184 * @param {Number} keyframe step number\r
185 */\r
186\r
187 /**\r
188 * @event afteranimate\r
189 * Fires when the animation is complete.\r
190 * @param {Ext.fx.Animator} this\r
191 * @param {Date} startTime\r
192 */\r
193\r
194 /**\r
195 * @cfg {Object} keyframes\r
196 * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'\r
197 * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using\r
198 * "from" or "to"</b>. A keyframe declaration without these keyframe selectors is invalid and will not be available for\r
199 * animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to\r
200 * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:\r
201 <pre><code>\r
202keyframes : {\r
203 '0%': {\r
204 left: 100\r
205 },\r
206 '40%': {\r
207 left: 150\r
208 },\r
209 '60%': {\r
210 left: 75\r
211 },\r
212 '100%': {\r
213 left: 100\r
214 }\r
215}\r
216 </code></pre>\r
217 */\r
218 constructor: function(config) {\r
219 var me = this;\r
220 config = Ext.apply(me, config || {});\r
221 me.config = config;\r
222 me.id = Ext.id(null, 'ext-animator-');\r
223\r
224 me.mixins.observable.constructor.call(me, config);\r
225 me.timeline = [];\r
226 me.createTimeline(me.keyframes);\r
227 if (me.target) {\r
228 me.applyAnimator(me.target);\r
229 Ext.fx.Manager.addAnim(me);\r
230 }\r
231 },\r
232\r
233 /**\r
234 * @private\r
235 */\r
236 sorter: function (a, b) {\r
237 return a.pct - b.pct;\r
238 },\r
239\r
240 /**\r
241 * @private\r
242 * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe\r
243 * or applying the 'to' configuration to all keyframes. Also calculates the proper animation duration per keyframe.\r
244 */\r
245 createTimeline: function(keyframes) {\r
246 var me = this,\r
247 attrs = [],\r
248 to = me.to || {},\r
249 duration = me.duration,\r
250 prevMs, ms, i, ln, pct, attr;\r
251\r
252 for (pct in keyframes) {\r
253 if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {\r
254 attr = {attrs: Ext.apply(keyframes[pct], to)};\r
255 // CSS3 spec allow for from/to to be specified.\r
256 if (pct === "from") {\r
257 pct = 0;\r
258 }\r
259 else if (pct === "to") {\r
260 pct = 100;\r
261 }\r
262 // convert % values into integers\r
263 attr.pct = parseInt(pct, 10);\r
264 attrs.push(attr);\r
265 }\r
266 }\r
267 // Sort by pct property\r
268 Ext.Array.sort(attrs, me.sorter);\r
269 // Only an end\r
270 //if (attrs[0].pct) {\r
271 // attrs.unshift({pct: 0, attrs: element.attrs});\r
272 //}\r
273\r
274 ln = attrs.length;\r
275 for (i = 0; i < ln; i++) {\r
276 prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;\r
277 ms = duration * (attrs[i].pct / 100);\r
278 me.timeline.push({\r
279 duration: ms - prevMs,\r
280 attrs: attrs[i].attrs\r
281 });\r
282 }\r
283 },\r
284\r
285 /**\r
286 * Applies animation to the Ext.fx.target\r
287 * @param {String/Object} target\r
288 * @private\r
289 */\r
290 applyAnimator: function(target) {\r
291 var me = this,\r
292 anims = [],\r
293 timeline = me.timeline,\r
294 ln = timeline.length,\r
295 anim, easing, damper, attrs, i;\r
296\r
297 if (me.fireEvent('beforeanimate', me) !== false) {\r
298 for (i = 0; i < ln; i++) {\r
299 anim = timeline[i];\r
300 attrs = anim.attrs;\r
301 easing = attrs.easing || me.easing;\r
302 damper = attrs.damper || me.damper;\r
303 delete attrs.easing;\r
304 delete attrs.damper;\r
305 anim = new Ext.fx.Anim({\r
306 target: target,\r
307 easing: easing,\r
308 damper: damper,\r
309 duration: anim.duration,\r
310 paused: true,\r
311 to: attrs\r
312 });\r
313 anims.push(anim);\r
314 }\r
315 me.animations = anims;\r
316 me.target = anim.target;\r
317 for (i = 0; i < ln - 1; i++) {\r
318 anim = anims[i];\r
319 anim.nextAnim = anims[i + 1];\r
320 anim.on('afteranimate', function() {\r
321 this.nextAnim.paused = false;\r
322 });\r
323 anim.on('afteranimate', function() {\r
324 this.fireEvent('keyframe', this, ++this.keyframeStep);\r
325 }, me);\r
326 }\r
327 anims[ln - 1].on('afteranimate', function() {\r
328 this.lastFrame();\r
329 }, me);\r
330 }\r
331 },\r
332\r
333 /**\r
334 * @private\r
335 * Fires beforeanimate and sets the running flag.\r
336 */\r
337 start: function(startTime) {\r
338 var me = this,\r
339 delay = me.delay,\r
340 delayStart = me.delayStart,\r
341 delayDelta;\r
342 if (delay) {\r
343 if (!delayStart) {\r
344 me.delayStart = startTime;\r
345 return;\r
346 }\r
347 else {\r
348 delayDelta = startTime - delayStart;\r
349 if (delayDelta < delay) {\r
350 return;\r
351 }\r
352 else {\r
353 // Compensate for frame delay;\r
354 startTime = new Date(delayStart.getTime() + delay);\r
355 }\r
356 }\r
357 }\r
358 if (me.fireEvent('beforeanimate', me) !== false) {\r
359 me.startTime = startTime;\r
360 me.running = true;\r
361 me.animations[me.keyframeStep].paused = false;\r
362 }\r
363 },\r
364\r
365 /**\r
366 * @private\r
367 * Perform lastFrame cleanup and handle iterations\r
368 * @return a hash of the new attributes.\r
369 */\r
370 lastFrame: function() {\r
371 var me = this,\r
372 iter = me.iterations,\r
373 iterCount = me.currentIteration;\r
374\r
375 iterCount++;\r
376 if (iterCount < iter) {\r
377 me.startTime = new Date();\r
378 me.currentIteration = iterCount;\r
379 me.keyframeStep = 0;\r
380 me.applyAnimator(me.target);\r
381 me.animations[me.keyframeStep].paused = false;\r
382 }\r
383 else {\r
384 me.currentIteration = 0;\r
385 me.end();\r
386 }\r
387 },\r
388\r
389 /**\r
390 * Fire afteranimate event and end the animation. Usually called automatically when the\r
391 * animation reaches its final frame, but can also be called manually to pre-emptively\r
392 * stop and destroy the running animation.\r
393 */\r
394 end: function() {\r
395 var me = this;\r
396 me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);\r
397 },\r
398 \r
399 isReady: function() {\r
400 return this.paused === false && this.running === false && this.iterations > 0;\r
401 },\r
402 \r
403 isRunning: function() {\r
404 // Explicitly return false, we don't want to be run continuously by the manager\r
405 return false;\r
406 }\r
407});\r