Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / fx / Anim.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.fx.Anim
17  *
18  * This class manages animation for a specific {@link #target}. The animation allows
19  * animation of various properties on the target, such as size, position, color and others.
20  *
21  * ## Starting Conditions
22  * The starting conditions for the animation are provided by the {@link #from} configuration.
23  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
24  * property is not defined, the starting value for that property will be read directly from the target.
25  *
26  * ## End Conditions
27  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
28  * the final values once the animations has finished. The values in the {@link #from} can mirror
29  * those in the {@link #to} configuration to provide a starting point.
30  *
31  * ## Other Options
32  *  - {@link #duration}: Specifies the time period of the animation.
33  *  - {@link #easing}: Specifies the easing of the animation.
34  *  - {@link #iterations}: Allows the animation to repeat a number of times.
35  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
36  *
37  * ## Example Code
38  *
39  *     @example
40  *     var myComponent = Ext.create('Ext.Component', {
41  *         renderTo: document.body,
42  *         width: 200,
43  *         height: 200,
44  *         style: 'border: 1px solid red;'
45  *     });
46  *
47  *     Ext.create('Ext.fx.Anim', {
48  *         target: myComponent,
49  *         duration: 1000,
50  *         from: {
51  *             width: 400 //starting width 400
52  *         },
53  *         to: {
54  *             width: 300, //end width 300
55  *             height: 300 // end width 300
56  *         }
57  *     });
58  */
59 Ext.define('Ext.fx.Anim', {
60
61     /* Begin Definitions */
62
63     mixins: {
64         observable: 'Ext.util.Observable'
65     },
66
67     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
68
69     /* End Definitions */
70
71     isAnimation: true,
72
73     /**
74      * @cfg {Function} callback
75      * A function to be run after the animation has completed.
76      */
77
78     /**
79      * @cfg {Function} scope
80      * The scope that the {@link #callback} function will be called with
81      */
82
83     /**
84      * @cfg {Number} duration
85      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
86      * specified, then each animate will take the same duration for each iteration.
87      */
88     duration: 250,
89
90     /**
91      * @cfg {Number} delay
92      * Time to delay before starting the animation. Defaults to 0.
93      */
94     delay: 0,
95
96     /* private used to track a delayed starting time */
97     delayStart: 0,
98
99     /**
100      * @cfg {Boolean} dynamic
101      * 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.
102      */
103     dynamic: false,
104
105     /**
106      * @cfg {String} easing
107 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
108 speed over its duration.
109
110          -backIn
111          -backOut
112          -bounceIn
113          -bounceOut
114          -ease
115          -easeIn
116          -easeOut
117          -easeInOut
118          -elasticIn
119          -elasticOut
120          -cubic-bezier(x1, y1, x2, y2)
121
122 Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
123 specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
124 be in the range [0, 1] or the definition is invalid.
125
126 [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
127
128      * @markdown
129      */
130     easing: 'ease',
131
132      /**
133       * @cfg {Object} keyframes
134       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
135       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
136       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
137       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
138       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
139  <pre><code>
140 keyframes : {
141     '0%': {
142         left: 100
143     },
144     '40%': {
145         left: 150
146     },
147     '60%': {
148         left: 75
149     },
150     '100%': {
151         left: 100
152     }
153 }
154  </code></pre>
155       */
156
157     /**
158      * @private
159      */
160     damper: 1,
161
162     /**
163      * @private
164      */
165     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
166
167     /**
168      * Run the animation from the end to the beginning
169      * Defaults to false.
170      * @cfg {Boolean} reverse
171      */
172     reverse: false,
173
174     /**
175      * Flag to determine if the animation has started
176      * @property running
177      * @type Boolean
178      */
179     running: false,
180
181     /**
182      * Flag to determine if the animation is paused. Only set this to true if you need to
183      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
184      * @property paused
185      * @type Boolean
186      */
187     paused: false,
188
189     /**
190      * Number of times to execute the animation. Defaults to 1.
191      * @cfg {Number} iterations
192      */
193     iterations: 1,
194
195     /**
196      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
197      * @cfg {Boolean} alternate
198      * Defaults to false.
199      */
200     alternate: false,
201
202     /**
203      * Current iteration the animation is running.
204      * @property currentIteration
205      * @type Number
206      */
207     currentIteration: 0,
208
209     /**
210      * Starting time of the animation.
211      * @property startTime
212      * @type Date
213      */
214     startTime: 0,
215
216     /**
217      * Contains a cache of the interpolators to be used.
218      * @private
219      * @property propHandlers
220      * @type Object
221      */
222
223     /**
224      * @cfg {String/Object} target
225      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
226      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
227      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
228      * automatically.
229      */
230
231     /**
232      * @cfg {Object} from
233      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
234      * Ext.fx.target will be used. For example:
235 <pre><code>
236 from : {
237     opacity: 0,       // Transparent
238     color: '#ffffff', // White
239     left: 0
240 }
241 </code></pre>
242      */
243
244     /**
245      * @cfg {Object} to
246      * An object containing property/value pairs for the end of the animation. For example:
247  <pre><code>
248  to : {
249      opacity: 1,       // Opaque
250      color: '#00ff00', // Green
251      left: 500
252  }
253  </code></pre>
254      */
255
256     // @private
257     constructor: function(config) {
258         var me = this,
259             curve;
260             
261         config = config || {};
262         // If keyframes are passed, they really want an Animator instead.
263         if (config.keyframes) {
264             return Ext.create('Ext.fx.Animator', config);
265         }
266         config = Ext.apply(me, config);
267         if (me.from === undefined) {
268             me.from = {};
269         }
270         me.propHandlers = {};
271         me.config = config;
272         me.target = Ext.fx.Manager.createTarget(me.target);
273         me.easingFn = Ext.fx.Easing[me.easing];
274         me.target.dynamic = me.dynamic;
275
276         // If not a pre-defined curve, try a cubic-bezier
277         if (!me.easingFn) {
278             me.easingFn = String(me.easing).match(me.bezierRE);
279             if (me.easingFn && me.easingFn.length == 5) {
280                 curve = me.easingFn;
281                 me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
282             }
283         }
284         me.id = Ext.id(null, 'ext-anim-');
285         Ext.fx.Manager.addAnim(me);
286         me.addEvents(
287             /**
288              * @event beforeanimate
289              * Fires before the animation starts. A handler can return false to cancel the animation.
290              * @param {Ext.fx.Anim} this
291              */
292             'beforeanimate',
293              /**
294               * @event afteranimate
295               * Fires when the animation is complete.
296               * @param {Ext.fx.Anim} this
297               * @param {Date} startTime
298               */
299             'afteranimate',
300              /**
301               * @event lastframe
302               * Fires when the animation's last frame has been set.
303               * @param {Ext.fx.Anim} this
304               * @param {Date} startTime
305               */
306             'lastframe'
307         );
308         me.mixins.observable.constructor.call(me, config);
309         if (config.callback) {
310             me.on('afteranimate', config.callback, config.scope);
311         }
312         return me;
313     },
314
315     /**
316      * @private
317      * Helper to the target
318      */
319     setAttr: function(attr, value) {
320         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
321     },
322
323     /**
324      * @private
325      * Set up the initial currentAttrs hash.
326      */
327     initAttrs: function() {
328         var me = this,
329             from = me.from,
330             to = me.to,
331             initialFrom = me.initialFrom || {},
332             out = {},
333             start, end, propHandler, attr;
334
335         for (attr in to) {
336             if (to.hasOwnProperty(attr)) {
337                 start = me.target.getAttr(attr, from[attr]);
338                 end = to[attr];
339                 // Use default (numeric) property handler
340                 if (!Ext.fx.PropertyHandler[attr]) {
341                     if (Ext.isObject(end)) {
342                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
343                     } else {
344                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
345                     }
346                 }
347                 // Use custom handler
348                 else {
349                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
350                 }
351                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
352             }
353         }
354         me.currentAttrs = out;
355     },
356
357     /**
358      * @private
359      * Fires beforeanimate and sets the running flag.
360      */
361     start: function(startTime) {
362         var me = this,
363             delay = me.delay,
364             delayStart = me.delayStart,
365             delayDelta;
366         if (delay) {
367             if (!delayStart) {
368                 me.delayStart = startTime;
369                 return;
370             }
371             else {
372                 delayDelta = startTime - delayStart;
373                 if (delayDelta < delay) {
374                     return;
375                 }
376                 else {
377                     // Compensate for frame delay;
378                     startTime = new Date(delayStart.getTime() + delay);
379                 }
380             }
381         }
382         if (me.fireEvent('beforeanimate', me) !== false) {
383             me.startTime = startTime;
384             if (!me.paused && !me.currentAttrs) {
385                 me.initAttrs();
386             }
387             me.running = true;
388         }
389     },
390
391     /**
392      * @private
393      * Calculate attribute value at the passed timestamp.
394      * @returns a hash of the new attributes.
395      */
396     runAnim: function(elapsedTime) {
397         var me = this,
398             attrs = me.currentAttrs,
399             duration = me.duration,
400             easingFn = me.easingFn,
401             propHandlers = me.propHandlers,
402             ret = {},
403             easing, values, attr, lastFrame;
404
405         if (elapsedTime >= duration) {
406             elapsedTime = duration;
407             lastFrame = true;
408         }
409         if (me.reverse) {
410             elapsedTime = duration - elapsedTime;
411         }
412
413         for (attr in attrs) {
414             if (attrs.hasOwnProperty(attr)) {
415                 values = attrs[attr];
416                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
417                 ret[attr] = propHandlers[attr].set(values, easing);
418             }
419         }
420         return ret;
421     },
422
423     /**
424      * @private
425      * Perform lastFrame cleanup and handle iterations
426      * @returns a hash of the new attributes.
427      */
428     lastFrame: function() {
429         var me = this,
430             iter = me.iterations,
431             iterCount = me.currentIteration;
432
433         iterCount++;
434         if (iterCount < iter) {
435             if (me.alternate) {
436                 me.reverse = !me.reverse;
437             }
438             me.startTime = new Date();
439             me.currentIteration = iterCount;
440             // Turn off paused for CSS3 Transitions
441             me.paused = false;
442         }
443         else {
444             me.currentIteration = 0;
445             me.end();
446             me.fireEvent('lastframe', me, me.startTime);
447         }
448     },
449
450     /**
451      * Fire afteranimate event and end the animation. Usually called automatically when the
452      * animation reaches its final frame, but can also be called manually to pre-emptively
453      * stop and destroy the running animation.
454      */
455     end: function() {
456         var me = this;
457         me.startTime = 0;
458         me.paused = false;
459         me.running = false;
460         Ext.fx.Manager.removeAnim(me);
461         me.fireEvent('afteranimate', me, me.startTime);
462     }
463 });
464 // Set flag to indicate that Fx is available. Class might not be available immediately.
465 Ext.enableFx = true;
466