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