eeab9b7a9f4086acc584dcb35acc8e4b0525d150
[jquery.git] / src / effects.js
1 (function( jQuery ) {
2
3 var elemdisplay = {},
4         rfxtypes = /toggle|show|hide/,
5         rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/,
6         rdashAlpha = /-([a-z])/ig,
7         timerId,
8         fxAttrs = [
9                 // height animations
10                 [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
11                 // width animations
12                 [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
13                 // opacity animations
14                 [ "opacity" ]
15         ],
16
17         fcamelCase = function( all, letter ) {
18                 return letter.toUpperCase();
19         };
20
21 jQuery.fn.extend({
22         show: function( speed, callback ) {
23                 if ( speed || speed === 0) {
24                         return this.animate( genFx("show", 3), speed, callback);
25
26                 } else {
27                         for ( var i = 0, l = this.length; i < l; i++ ) {
28                                 var old = jQuery.data(this[i], "olddisplay");
29
30                                 this[i].style.display = old || "";
31
32                                 if ( jQuery.css( this[i], "display" ) === "none" ) {
33                                         var nodeName = this[i].nodeName, display;
34
35                                         if ( elemdisplay[ nodeName ] ) {
36                                                 display = elemdisplay[ nodeName ];
37
38                                         } else {
39                                                 var elem = jQuery("<" + nodeName + " />").appendTo("body");
40
41                                                 display = elem.css("display");
42
43                                                 if ( display === "none" ) {
44                                                         display = "block";
45                                                 }
46
47                                                 elem.remove();
48
49                                                 elemdisplay[ nodeName ] = display;
50                                         }
51
52                                         jQuery.data(this[i], "olddisplay", display);
53                                 }
54                         }
55
56                         // Set the display of the elements in a second loop
57                         // to avoid the constant reflow
58                         for ( var j = 0, k = this.length; j < k; j++ ) {
59                                 this[j].style.display = jQuery.data(this[j], "olddisplay") || "";
60                         }
61
62                         return this;
63                 }
64         },
65
66         hide: function( speed, callback ) {
67                 if ( speed || speed === 0 ) {
68                         return this.animate( genFx("hide", 3), speed, callback);
69
70                 } else {
71                         for ( var i = 0, l = this.length; i < l; i++ ) {
72                                 var old = jQuery.data(this[i], "olddisplay");
73                                 if ( !old && old !== "none" ) {
74                                         jQuery.data( this[i], "olddisplay", jQuery.css( this[i], "display" ) );
75                                 }
76                         }
77
78                         // Set the display of the elements in a second loop
79                         // to avoid the constant reflow
80                         for ( var j = 0, k = this.length; j < k; j++ ) {
81                                 this[j].style.display = "none";
82                         }
83
84                         return this;
85                 }
86         },
87
88         // Save the old toggle function
89         _toggle: jQuery.fn.toggle,
90
91         toggle: function( fn, fn2 ) {
92                 var bool = typeof fn === "boolean";
93
94                 if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
95                         this._toggle.apply( this, arguments );
96
97                 } else if ( fn == null || bool ) {
98                         this.each(function() {
99                                 var state = bool ? fn : jQuery(this).is(":hidden");
100                                 jQuery(this)[ state ? "show" : "hide" ]();
101                         });
102
103                 } else {
104                         this.animate(genFx("toggle", 3), fn, fn2);
105                 }
106
107                 return this;
108         },
109
110         fadeTo: function( speed, to, callback ) {
111                 return this.filter(":hidden").css("opacity", 0).show().end()
112                                         .animate({opacity: to}, speed, callback);
113         },
114
115         animate: function( prop, speed, easing, callback ) {
116                 var optall = jQuery.speed(speed, easing, callback);
117
118                 if ( jQuery.isEmptyObject( prop ) ) {
119                         return this.each( optall.complete );
120                 }
121
122                 return this[ optall.queue === false ? "each" : "queue" ](function() {
123                         var opt = jQuery.extend({}, optall), p,
124                                 hidden = this.nodeType === 1 && jQuery(this).is(":hidden"),
125                                 self = this;
126
127                         for ( p in prop ) {
128                                 var name = p.replace(rdashAlpha, fcamelCase);
129
130                                 if ( p !== name ) {
131                                         prop[ name ] = prop[ p ];
132                                         delete prop[ p ];
133                                         p = name;
134                                 }
135
136                                 if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
137                                         return opt.complete.call(this);
138                                 }
139
140                                 if ( ( p === "height" || p === "width" ) && this.style ) {
141                                         // Store display property
142                                         opt.display = this.style.display;
143
144                                         // Make sure that nothing sneaks out
145                                         opt.overflow = this.style.overflow;
146                                 }
147
148                                 if ( jQuery.isArray( prop[p] ) ) {
149                                         // Create (if needed) and add to specialEasing
150                                         (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
151                                         prop[p] = prop[p][0];
152                                 }
153                         }
154
155                         if ( opt.overflow != null ) {
156                                 this.style.overflow = "hidden";
157                         }
158
159                         opt.curAnim = jQuery.extend({}, prop);
160
161                         jQuery.each( prop, function( name, val ) {
162                                 var e = new jQuery.fx( self, opt, name );
163
164                                 if ( rfxtypes.test(val) ) {
165                                         e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
166
167                                 } else {
168                                         var parts = rfxnum.exec(val),
169                                                 start = e.cur(true) || 0;
170
171                                         if ( parts ) {
172                                                 var end = parseFloat( parts[2] ),
173                                                         unit = parts[3] || "px";
174
175                                                 // We need to compute starting value
176                                                 if ( unit !== "px" ) {
177                                                         self.style[ name ] = (end || 1) + unit;
178                                                         start = ((end || 1) / e.cur(true)) * start;
179                                                         self.style[ name ] = start + unit;
180                                                 }
181
182                                                 // If a +=/-= token was provided, we're doing a relative animation
183                                                 if ( parts[1] ) {
184                                                         end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
185                                                 }
186
187                                                 e.custom( start, end, unit );
188
189                                         } else {
190                                                 e.custom( start, val, "" );
191                                         }
192                                 }
193                         });
194
195                         // For JS strict compliance
196                         return true;
197                 });
198         },
199
200         stop: function( clearQueue, gotoEnd ) {
201                 var timers = jQuery.timers;
202
203                 if ( clearQueue ) {
204                         this.queue([]);
205                 }
206
207                 this.each(function() {
208                         // go in reverse order so anything added to the queue during the loop is ignored
209                         for ( var i = timers.length - 1; i >= 0; i-- ) {
210                                 if ( timers[i].elem === this ) {
211                                         if (gotoEnd) {
212                                                 // force the next step to be the last
213                                                 timers[i](true);
214                                         }
215
216                                         timers.splice(i, 1);
217                                 }
218                         }
219                 });
220
221                 // start the next in the queue if the last step wasn't forced
222                 if ( !gotoEnd ) {
223                         this.dequeue();
224                 }
225
226                 return this;
227         }
228
229 });
230
231 function genFx( type, num ) {
232         var obj = {};
233
234         jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
235                 obj[ this ] = type;
236         });
237
238         return obj;
239 }
240
241 // Generate shortcuts for custom animations
242 jQuery.each({
243         slideDown: genFx("show", 1),
244         slideUp: genFx("hide", 1),
245         slideToggle: genFx("toggle", 1),
246         fadeIn: { opacity: "show" },
247         fadeOut: { opacity: "hide" }
248 }, function( name, props ) {
249         jQuery.fn[ name ] = function( speed, callback ) {
250                 return this.animate( props, speed, callback );
251         };
252 });
253
254 jQuery.extend({
255         speed: function( speed, easing, fn ) {
256                 var opt = speed && typeof speed === "object" ? speed : {
257                         complete: fn || !fn && easing ||
258                                 jQuery.isFunction( speed ) && speed,
259                         duration: speed,
260                         easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
261                 };
262
263                 opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
264                         jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
265
266                 // Queueing
267                 opt.old = opt.complete;
268                 opt.complete = function() {
269                         if ( opt.queue !== false ) {
270                                 jQuery(this).dequeue();
271                         }
272                         if ( jQuery.isFunction( opt.old ) ) {
273                                 opt.old.call( this );
274                         }
275                 };
276
277                 return opt;
278         },
279
280         easing: {
281                 linear: function( p, n, firstNum, diff ) {
282                         return firstNum + diff * p;
283                 },
284                 swing: function( p, n, firstNum, diff ) {
285                         return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
286                 }
287         },
288
289         timers: [],
290
291         fx: function( elem, options, prop ) {
292                 this.options = options;
293                 this.elem = elem;
294                 this.prop = prop;
295
296                 if ( !options.orig ) {
297                         options.orig = {};
298                 }
299         }
300
301 });
302
303 jQuery.fx.prototype = {
304         // Simple function for setting a style value
305         update: function() {
306                 if ( this.options.step ) {
307                         this.options.step.call( this.elem, this.now, this );
308                 }
309
310                 (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
311
312                 // Set display property to block for height/width animations
313                 if ( ( this.prop === "height" || this.prop === "width" ) && this.elem.style ) {
314                         this.elem.style.display = "block";
315                 }
316         },
317
318         // Get the current size
319         cur: function() {
320                 if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
321                         return this.elem[ this.prop ];
322                 }
323
324                 var r = jQuery.css( this.elem, this.prop );
325                 return r && r > -10000 ? r : 0;
326         },
327
328         // Start an animation from one number to another
329         custom: function( from, to, unit ) {
330                 this.startTime = jQuery.now();
331                 this.start = from;
332                 this.end = to;
333                 this.unit = unit || this.unit || "px";
334                 this.now = this.start;
335                 this.pos = this.state = 0;
336
337                 var self = this;
338                 function t( gotoEnd ) {
339                         return self.step(gotoEnd);
340                 }
341
342                 t.elem = this.elem;
343
344                 if ( t() && jQuery.timers.push(t) && !timerId ) {
345                         timerId = setInterval(jQuery.fx.tick, 13);
346                 }
347         },
348
349         // Simple 'show' function
350         show: function() {
351                 // Remember where we started, so that we can go back to it later
352                 this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
353                 this.options.show = true;
354
355                 // Begin the animation
356                 // Make sure that we start at a small width/height to avoid any
357                 // flash of content
358                 this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
359
360                 // Start by showing the element
361                 jQuery( this.elem ).show();
362         },
363
364         // Simple 'hide' function
365         hide: function() {
366                 // Remember where we started, so that we can go back to it later
367                 this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
368                 this.options.hide = true;
369
370                 // Begin the animation
371                 this.custom(this.cur(), 0);
372         },
373
374         // Each step of an animation
375         step: function( gotoEnd ) {
376                 var t = jQuery.now(), done = true;
377
378                 if ( gotoEnd || t >= this.options.duration + this.startTime ) {
379                         this.now = this.end;
380                         this.pos = this.state = 1;
381                         this.update();
382
383                         this.options.curAnim[ this.prop ] = true;
384
385                         for ( var i in this.options.curAnim ) {
386                                 if ( this.options.curAnim[i] !== true ) {
387                                         done = false;
388                                 }
389                         }
390
391                         if ( done ) {
392                                 if ( this.options.display != null ) {
393                                         // Reset the overflow
394                                         this.elem.style.overflow = this.options.overflow;
395
396                                         // Reset the display
397                                         var old = jQuery.data(this.elem, "olddisplay");
398                                         this.elem.style.display = old ? old : this.options.display;
399
400                                         if ( jQuery.css( this.elem, "display" ) === "none" ) {
401                                                 this.elem.style.display = "block";
402                                         }
403                                 }
404
405                                 // Hide the element if the "hide" operation was done
406                                 if ( this.options.hide ) {
407                                         jQuery(this.elem).hide();
408                                 }
409
410                                 // Reset the properties, if the item has been hidden or shown
411                                 if ( this.options.hide || this.options.show ) {
412                                         for ( var p in this.options.curAnim ) {
413                                                 jQuery.style( this.elem, p, this.options.orig[p] );
414                                         }
415                                 }
416
417                                 // Execute the complete function
418                                 this.options.complete.call( this.elem );
419                         }
420
421                         return false;
422
423                 } else {
424                         var n = t - this.startTime;
425                         this.state = n / this.options.duration;
426
427                         // Perform the easing function, defaults to swing
428                         var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
429                         var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
430                         this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
431                         this.now = this.start + ((this.end - this.start) * this.pos);
432
433                         // Perform the next step of the animation
434                         this.update();
435                 }
436
437                 return true;
438         }
439 };
440
441 jQuery.extend( jQuery.fx, {
442         tick: function() {
443                 var timers = jQuery.timers;
444
445                 for ( var i = 0; i < timers.length; i++ ) {
446                         if ( !timers[i]() ) {
447                                 timers.splice(i--, 1);
448                         }
449                 }
450
451                 if ( !timers.length ) {
452                         jQuery.fx.stop();
453                 }
454         },
455                 
456         stop: function() {
457                 clearInterval( timerId );
458                 timerId = null;
459         },
460         
461         speeds: {
462                 slow: 600,
463                 fast: 200,
464                 // Default speed
465                 _default: 400
466         },
467
468         step: {
469                 opacity: function( fx ) {
470                         jQuery.style( fx.elem, "opacity", fx.now );
471                 },
472
473                 _default: function( fx ) {
474                         if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
475                                 fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
476                         } else {
477                                 fx.elem[ fx.prop ] = fx.now;
478                         }
479                 }
480         }
481 });
482
483 if ( jQuery.expr && jQuery.expr.filters ) {
484         jQuery.expr.filters.animated = function( elem ) {
485                 return jQuery.grep(jQuery.timers, function( fn ) {
486                         return elem === fn.elem;
487                 }).length;
488         };
489 }
490
491 })( jQuery );