]>
Commit | Line | Data |
---|---|---|
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 | |
58 | Ext.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 | |
202 | keyframes : {\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 |