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