Fix flicker in animations
[jquery.git] / src / fx / fx.js
1 jQuery.fn.extend({
2
3         /**
4          * Displays each of the set of matched elements if they are hidden.
5          *
6          * @example $("p").show()
7          * @before <p style="display: none">Hello</p>
8          * @result [ <p style="display: block">Hello</p> ]
9          *
10          * @name show
11          * @type jQuery
12          * @cat Effects
13          */
14         
15         /**
16          * Show all matched elements using a graceful animation and firing an
17          * optional callback after completion.
18          *
19          * The height, width, and opacity of each of the matched elements 
20          * are changed dynamically according to the specified speed.
21          *
22          * @example $("p").show("slow");
23          *
24          * @example $("p").show("slow",function(){
25          *   alert("Animation Done.");
26          * });
27          *
28          * @name show
29          * @type jQuery
30          * @param String|Number speed A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
31          * @param Function callback (optional) A function to be executed whenever the animation completes.
32          * @cat Effects
33          * @see hide(String|Number,Function)
34          */
35         show: function(speed,callback){
36                 var hidden = this.filter(":hidden");
37                 speed ?
38                         hidden.animate({
39                                 height: "show", width: "show", opacity: "show"
40                         }, speed, callback) :
41                         
42                         hidden.each(function(){
43                                 this.style.display = this.oldblock ? this.oldblock : "";
44                                 if ( jQuery.css(this,"display") == "none" )
45                                         this.style.display = "block";
46                         });
47                 return this;
48         },
49         
50         /**
51          * Hides each of the set of matched elements if they are shown.
52          *
53          * @example $("p").hide()
54          * @before <p>Hello</p>
55          * @result [ <p style="display: none">Hello</p> ]
56          *
57          * @name hide
58          * @type jQuery
59          * @cat Effects
60          */
61         
62         /**
63          * Hide all matched elements using a graceful animation and firing an
64          * optional callback after completion.
65          *
66          * The height, width, and opacity of each of the matched elements 
67          * are changed dynamically according to the specified speed.
68          *
69          * @example $("p").hide("slow");
70          *
71          * @example $("p").hide("slow",function(){
72          *   alert("Animation Done.");
73          * });
74          *
75          * @name hide
76          * @type jQuery
77          * @param String|Number speed A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
78          * @param Function callback (optional) A function to be executed whenever the animation completes.
79          * @cat Effects
80          * @see show(String|Number,Function)
81          */
82         hide: function(speed,callback){
83                 var visible = this.filter(":visible");
84                 speed ?
85                         visible.animate({
86                                 height: "hide", width: "hide", opacity: "hide"
87                         }, speed, callback) :
88                         
89                         visible.each(function(){
90                                 this.oldblock = this.oldblock || jQuery.css(this,"display");
91                                 if ( this.oldblock == "none" )
92                                         this.oldblock = "block";
93                                 this.style.display = "none";
94                         });
95                 return this;
96         },
97
98         // Save the old toggle function
99         _toggle: jQuery.fn.toggle,
100         
101         /**
102          * Toggles each of the set of matched elements. If they are shown,
103          * toggle makes them hidden. If they are hidden, toggle
104          * makes them shown.
105          *
106          * @example $("p").toggle()
107          * @before <p>Hello</p><p style="display: none">Hello Again</p>
108          * @result [ <p style="display: none">Hello</p>, <p style="display: block">Hello Again</p> ]
109          *
110          * @name toggle
111          * @type jQuery
112          * @cat Effects
113          */
114         toggle: function( fn, fn2 ){
115                 var args = arguments;
116                 return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
117                         this._toggle( fn, fn2 ) :
118                         this.each(function(){
119                                 jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]
120                                         .apply( jQuery(this), args );
121                         });
122         },
123         
124         /**
125          * Reveal all matched elements by adjusting their height and firing an
126          * optional callback after completion.
127          *
128          * Only the height is adjusted for this animation, causing all matched
129          * elements to be revealed in a "sliding" manner.
130          *
131          * @example $("p").slideDown("slow");
132          *
133          * @example $("p").slideDown("slow",function(){
134          *   alert("Animation Done.");
135          * });
136          *
137          * @name slideDown
138          * @type jQuery
139          * @param String|Number speed (optional) A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
140          * @param Function callback (optional) A function to be executed whenever the animation completes.
141          * @cat Effects
142          * @see slideUp(String|Number,Function)
143          * @see slideToggle(String|Number,Function)
144          */
145         slideDown: function(speed,callback){
146                 return this.animate({height: "show"}, speed, callback);
147         },
148         
149         /**
150          * Hide all matched elements by adjusting their height and firing an
151          * optional callback after completion.
152          *
153          * Only the height is adjusted for this animation, causing all matched
154          * elements to be hidden in a "sliding" manner.
155          *
156          * @example $("p").slideUp("slow");
157          *
158          * @example $("p").slideUp("slow",function(){
159          *   alert("Animation Done.");
160          * });
161          *
162          * @name slideUp
163          * @type jQuery
164          * @param String|Number speed (optional) A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
165          * @param Function callback (optional) A function to be executed whenever the animation completes.
166          * @cat Effects
167          * @see slideDown(String|Number,Function)
168          * @see slideToggle(String|Number,Function)
169          */
170         slideUp: function(speed,callback){
171                 return this.animate({height: "hide"}, speed, callback);
172         },
173
174         /**
175          * Toggle the visibility of all matched elements by adjusting their height and firing an
176          * optional callback after completion.
177          *
178          * Only the height is adjusted for this animation, causing all matched
179          * elements to be hidden in a "sliding" manner.
180          *
181          * @example $("p").slideToggle("slow");
182          *
183          * @example $("p").slideToggle("slow",function(){
184          *   alert("Animation Done.");
185          * });
186          *
187          * @name slideToggle
188          * @type jQuery
189          * @param String|Number speed (optional) A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
190          * @param Function callback (optional) A function to be executed whenever the animation completes.
191          * @cat Effects
192          * @see slideDown(String|Number,Function)
193          * @see slideUp(String|Number,Function)
194          */
195         slideToggle: function(speed, callback){
196                 return this.each(function(){
197                         var state = jQuery(this).is(":hidden") ? "show" : "hide";
198                         jQuery(this).animate({height: state}, speed, callback);
199                 });
200         },
201         
202         /**
203          * Fade in all matched elements by adjusting their opacity and firing an
204          * optional callback after completion.
205          *
206          * Only the opacity is adjusted for this animation, meaning that
207          * all of the matched elements should already have some form of height
208          * and width associated with them.
209          *
210          * @example $("p").fadeIn("slow");
211          *
212          * @example $("p").fadeIn("slow",function(){
213          *   alert("Animation Done.");
214          * });
215          *
216          * @name fadeIn
217          * @type jQuery
218          * @param String|Number speed (optional) A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
219          * @param Function callback (optional) A function to be executed whenever the animation completes.
220          * @cat Effects
221          * @see fadeOut(String|Number,Function)
222          * @see fadeTo(String|Number,Number,Function)
223          */
224         fadeIn: function(speed, callback){
225                 return this.animate({opacity: "show"}, speed, callback);
226         },
227         
228         /**
229          * Fade out all matched elements by adjusting their opacity and firing an
230          * optional callback after completion.
231          *
232          * Only the opacity is adjusted for this animation, meaning that
233          * all of the matched elements should already have some form of height
234          * and width associated with them.
235          *
236          * @example $("p").fadeOut("slow");
237          *
238          * @example $("p").fadeOut("slow",function(){
239          *   alert("Animation Done.");
240          * });
241          *
242          * @name fadeOut
243          * @type jQuery
244          * @param String|Number speed (optional) A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
245          * @param Function callback (optional) A function to be executed whenever the animation completes.
246          * @cat Effects
247          * @see fadeIn(String|Number,Function)
248          * @see fadeTo(String|Number,Number,Function)
249          */
250         fadeOut: function(speed, callback){
251                 return this.animate({opacity: "hide"}, speed, callback);
252         },
253         
254         /**
255          * Fade the opacity of all matched elements to a specified opacity and firing an
256          * optional callback after completion.
257          *
258          * Only the opacity is adjusted for this animation, meaning that
259          * all of the matched elements should already have some form of height
260          * and width associated with them.
261          *
262          * @example $("p").fadeTo("slow", 0.5);
263          *
264          * @example $("p").fadeTo("slow", 0.5, function(){
265          *   alert("Animation Done.");
266          * });
267          *
268          * @name fadeTo
269          * @type jQuery
270          * @param String|Number speed A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
271          * @param Number opacity The opacity to fade to (a number from 0 to 1).
272          * @param Function callback (optional) A function to be executed whenever the animation completes.
273          * @cat Effects
274          * @see fadeIn(String|Number,Function)
275          * @see fadeOut(String|Number,Function)
276          */
277         fadeTo: function(speed,to,callback){
278                 return this.animate({opacity: to}, speed, callback);
279         },
280         
281         /**
282          * A function for making your own, custom, animations. The key aspect of
283          * this function is the object of style properties that will be animated,
284          * and to what end. Each key within the object represents a style property
285          * that will also be animated (for example: "height", "top", or "opacity").
286          *
287          * The value associated with the key represents to what end the property
288          * will be animated. If a number is provided as the value, then the style
289          * property will be transitioned from its current state to that new number.
290          * Oterwise if the string "hide", "show", or "toggle" is provided, a default
291          * animation will be constructed for that property.
292          *
293          * @example $("p").animate({
294          *   height: 'toggle', opacity: 'toggle'
295          * }, "slow");
296          *
297          * @example $("p").animate({
298          *   left: 50, opacity: 'show'
299          * }, 500);
300          *
301          * @example $("p").animate({
302          *   opacity: 'show'
303          * }, "slow", "easein");
304          * @desc An example of using an 'easing' function to provide a different style of animation. This will only work if you have a plugin that provides this easing function (Only 'linear' is provided by default, with jQuery).
305          *
306          * @name animate
307          * @type jQuery
308          * @param Hash params A set of style attributes that you wish to animate, and to what end.
309          * @param String|Number speed (optional) A string representing one of the three predefined speeds ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000).
310          * @param String easing (optional) The name of the easing effect that you want to use (Plugin Required).
311          * @param Function callback (optional) A function to be executed whenever the animation completes.
312          * @cat Effects
313          */
314         animate: function( prop, speed, easing, callback ) {
315                 return this.queue(function(){
316                 
317                         this.curAnim = jQuery.extend({}, prop);
318                         var opt = jQuery.speed(speed, easing, callback);
319                         
320                         for ( var p in prop ) {
321                                 var e = new jQuery.fx( this, opt, p );
322                                 if ( prop[p].constructor == Number )
323                                         e.custom( e.cur(), prop[p] );
324                                 else
325                                         e[ prop[p] ]( prop );
326                         }
327                         
328                 });
329         },
330         
331         /**
332          *
333          * @private
334          */
335         queue: function(type,fn){
336                 if ( !fn ) {
337                         fn = type;
338                         type = "fx";
339                 }
340         
341                 return this.each(function(){
342                         if ( !this.queue )
343                                 this.queue = {};
344         
345                         if ( !this.queue[type] )
346                                 this.queue[type] = [];
347         
348                         this.queue[type].push( fn );
349                 
350                         if ( this.queue[type].length == 1 )
351                                 fn.apply(this);
352                 });
353         }
354
355 });
356
357 jQuery.extend({
358         
359         speed: function(speed, easing, fn) {
360                 var opt = speed && speed.constructor == Object ? speed : {
361                         complete: fn || !fn && easing || 
362                                 jQuery.isFunction( speed ) && speed,
363                         duration: speed,
364                         easing: fn && easing || easing && easing.constructor != Function && easing
365                 };
366
367                 opt.duration = (opt.duration && opt.duration.constructor == Number ? 
368                         opt.duration : 
369                         { slow: 600, fast: 200 }[opt.duration]) || 400;
370         
371                 // Queueing
372                 opt.old = opt.complete;
373                 opt.complete = function(){
374                         jQuery.dequeue(this, "fx");
375                         if ( jQuery.isFunction( opt.old ) )
376                                 opt.old.apply( this );
377                 };
378         
379                 return opt;
380         },
381         
382         easing: {},
383         
384         queue: {},
385         
386         dequeue: function(elem,type){
387                 type = type || "fx";
388         
389                 if ( elem.queue && elem.queue[type] ) {
390                         // Remove self
391                         elem.queue[type].shift();
392         
393                         // Get next function
394                         var f = elem.queue[type][0];
395                 
396                         if ( f ) f.apply( elem );
397                 }
398         },
399
400         /*
401          * I originally wrote fx() as a clone of moo.fx and in the process
402          * of making it small in size the code became illegible to sane
403          * people. You've been warned.
404          */
405         
406         fx: function( elem, options, prop ){
407
408                 var z = this;
409
410                 // The styles
411                 var y = elem.style;
412                 
413                 // Store display property
414                 var oldDisplay = jQuery.css(elem, "display");
415
416                 // Make sure that nothing sneaks out
417                 y.overflow = "hidden";
418
419                 // Simple function for setting a style value
420                 z.a = function(){
421                         if ( options.step )
422                                 options.step.apply( elem, [ z.now ] );
423
424                         if ( prop == "opacity" )
425                                 jQuery.attr(y, "opacity", z.now); // Let attr handle opacity
426                         else if ( parseInt(z.now) ) // My hate for IE will never die
427                                 y[prop] = parseInt(z.now) + "px";
428                         
429                         y.display = "block"; // Set display property to block for animation
430                 };
431
432                 // Figure out the maximum number to run to
433                 z.max = function(){
434                         return parseFloat( jQuery.css(elem,prop) );
435                 };
436
437                 // Get the current size
438                 z.cur = function(){
439                         var r = parseFloat( jQuery.curCSS(elem, prop) );
440                         return r && r > -10000 ? r : z.max();
441                 };
442
443                 // Start an animation from one number to another
444                 z.custom = function(from,to){
445                         z.startTime = (new Date()).getTime();
446                         z.now = from;
447                         z.a();
448
449                         z.timer = setInterval(function(){
450                                 z.step(from, to);
451                         }, 13);
452                 };
453
454                 // Simple 'show' function
455                 z.show = function(){
456                         if ( !elem.orig ) elem.orig = {};
457
458                         // Remember where we started, so that we can go back to it later
459                         elem.orig[prop] = this.cur();
460
461                         options.show = true;
462
463                         // Begin the animation
464                         z.custom(0, elem.orig[prop]);
465
466                         // Stupid IE, look what you made me do
467                         if ( prop != "opacity" )
468                                 y[prop] = "1px";
469                 };
470
471                 // Simple 'hide' function
472                 z.hide = function(){
473                         if ( !elem.orig ) elem.orig = {};
474
475                         // Remember where we started, so that we can go back to it later
476                         elem.orig[prop] = this.cur();
477
478                         options.hide = true;
479
480                         // Begin the animation
481                         z.custom(elem.orig[prop], 0);
482                 };
483                 
484                 //Simple 'toggle' function
485                 z.toggle = function() {
486                         if ( !elem.orig ) elem.orig = {};
487
488                         // Remember where we started, so that we can go back to it later
489                         elem.orig[prop] = this.cur();
490
491                         if(oldDisplay == "none")  {
492                                 options.show = true;
493                                 
494                                 // Stupid IE, look what you made me do
495                                 if ( prop != "opacity" )
496                                         y[prop] = "1px";
497
498                                 // Begin the animation
499                                 z.custom(0, elem.orig[prop]);   
500                         } else {
501                                 options.hide = true;
502
503                                 // Begin the animation
504                                 z.custom(elem.orig[prop], 0);
505                         }               
506                 };
507
508                 // Each step of an animation
509                 z.step = function(firstNum, lastNum){
510                         var t = (new Date()).getTime();
511
512                         if (t > options.duration + z.startTime) {
513                                 // Stop the timer
514                                 clearInterval(z.timer);
515                                 z.timer = null;
516
517                                 z.now = lastNum;
518                                 z.a();
519
520                                 if (elem.curAnim) elem.curAnim[ prop ] = true;
521
522                                 var done = true;
523                                 for ( var i in elem.curAnim )
524                                         if ( elem.curAnim[i] !== true )
525                                                 done = false;
526
527                                 if ( done ) {
528                                         // Reset the overflow
529                                         y.overflow = "";
530                                         
531                                         // Reset the display
532                                         y.display = oldDisplay;
533                                         if (jQuery.css(elem, "display") == "none")
534                                                 y.display = "block";
535
536                                         // Hide the element if the "hide" operation was done
537                                         if ( options.hide ) 
538                                                 y.display = "none";
539
540                                         // Reset the properties, if the item has been hidden or shown
541                                         if ( options.hide || options.show )
542                                                 for ( var p in elem.curAnim )
543                                                         if (p == "opacity")
544                                                                 jQuery.attr(y, p, elem.orig[p]);
545                                                         else
546                                                                 y[p] = "";
547                                 }
548
549                                 // If a callback was provided, execute it
550                                 if ( done && jQuery.isFunction( options.complete ) )
551                                         // Execute the complete function
552                                         options.complete.apply( elem );
553                         } else {
554                                 var n = t - this.startTime;
555                                 // Figure out where in the animation we are and set the number
556                                 var p = n / options.duration;
557                                 
558                                 // If the easing function exists, then use it 
559                                 z.now = options.easing && jQuery.easing[options.easing] ?
560                                         jQuery.easing[options.easing](p, n,  firstNum, (lastNum-firstNum), options.duration) :
561                                         // else use default linear easing
562                                         ((-Math.cos(p*Math.PI)/2) + 0.5) * (lastNum-firstNum) + firstNum;
563
564                                 // Perform the next step of the animation
565                                 z.a();
566                         }
567                 };
568         
569         }
570 });