Landing pull request 511. Adding a little Makefile jQuery sizing utility to easily...
[jquery.git] / src / effects.js
blob8009171514359ea79561db29bf5a62ff9b158566
1 (function( jQuery ) {
3 var elemdisplay = {},
4         iframe, iframeDoc,
5         rfxtypes = /^(?:toggle|show|hide)$/,
6         rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
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         fxNow;
18 jQuery.fn.extend({
19         show: function( speed, easing, callback ) {
20                 var elem, display;
22                 if ( speed || speed === 0 ) {
23                         return this.animate( genFx("show", 3), speed, easing, callback);
25                 } else {
26                         for ( var i = 0, j = this.length; i < j; i++ ) {
27                                 elem = this[i];
29                                 if ( elem.style ) {
30                                         display = elem.style.display;
32                                         // Reset the inline display of this element to learn if it is
33                                         // being hidden by cascaded rules or not
34                                         if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
35                                                 display = elem.style.display = "";
36                                         }
38                                         // Set elements which have been overridden with display: none
39                                         // in a stylesheet to whatever the default browser style is
40                                         // for such an element
41                                         if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
42                                                 jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
43                                         }
44                                 }
45                         }
47                         // Set the display of most of the elements in a second loop
48                         // to avoid the constant reflow
49                         for ( i = 0; i < j; i++ ) {
50                                 elem = this[i];
52                                 if ( elem.style ) {
53                                         display = elem.style.display;
55                                         if ( display === "" || display === "none" ) {
56                                                 elem.style.display = jQuery._data(elem, "olddisplay") || "";
57                                         }
58                                 }
59                         }
61                         return this;
62                 }
63         },
65         hide: function( speed, easing, callback ) {
66                 if ( speed || speed === 0 ) {
67                         return this.animate( genFx("hide", 3), speed, easing, callback);
69                 } else {
70                         for ( var i = 0, j = this.length; i < j; i++ ) {
71                                 if ( this[i].style ) {
72                                         var display = jQuery.css( this[i], "display" );
74                                         if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
75                                                 jQuery._data( this[i], "olddisplay", display );
76                                         }
77                                 }
78                         }
80                         // Set the display of the elements in a second loop
81                         // to avoid the constant reflow
82                         for ( i = 0; i < j; i++ ) {
83                                 if ( this[i].style ) {
84                                         this[i].style.display = "none";
85                                 }
86                         }
88                         return this;
89                 }
90         },
92         // Save the old toggle function
93         _toggle: jQuery.fn.toggle,
95         toggle: function( fn, fn2, callback ) {
96                 var bool = typeof fn === "boolean";
98                 if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
99                         this._toggle.apply( this, arguments );
101                 } else if ( fn == null || bool ) {
102                         this.each(function() {
103                                 var state = bool ? fn : jQuery(this).is(":hidden");
104                                 jQuery(this)[ state ? "show" : "hide" ]();
105                         });
107                 } else {
108                         this.animate(genFx("toggle", 3), fn, fn2, callback);
109                 }
111                 return this;
112         },
114         fadeTo: function( speed, to, easing, callback ) {
115                 return this.filter(":hidden").css("opacity", 0).show().end()
116                                         .animate({opacity: to}, speed, easing, callback);
117         },
119         animate: function( prop, speed, easing, callback ) {
120                 var optall = jQuery.speed(speed, easing, callback);
122                 if ( jQuery.isEmptyObject( prop ) ) {
123                         return this.each( optall.complete, [ false ] );
124                 }
126                 // Do not change referenced properties as per-property easing will be lost
127                 prop = jQuery.extend( {}, prop );
129                 return this[ optall.queue === false ? "each" : "queue" ](function() {
130                         // XXX 'this' does not always have a nodeName when running the
131                         // test suite
133                         if ( optall.queue === false ) {
134                                 jQuery._mark( this );
135                         }
137                         var opt = jQuery.extend( {}, optall ),
138                                 isElement = this.nodeType === 1,
139                                 hidden = isElement && jQuery(this).is(":hidden"),
140                                 name, val, p,
141                                 display, e,
142                                 parts, start, end, unit;
144                         // will store per property easing and be used to determine when an animation is complete
145                         opt.animatedProperties = {};
147                         for ( p in prop ) {
149                                 // property name normalization
150                                 name = jQuery.camelCase( p );
151                                 if ( p !== name ) {
152                                         prop[ name ] = prop[ p ];
153                                         delete prop[ p ];
154                                 }
156                                 val = prop[ name ];
158                                 // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
159                                 if ( jQuery.isArray( val ) ) {
160                                         opt.animatedProperties[ name ] = val[ 1 ];
161                                         val = prop[ name ] = val[ 0 ];
162                                 } else {
163                                         opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
164                                 }
166                                 if ( val === "hide" && hidden || val === "show" && !hidden ) {
167                                         return opt.complete.call( this );
168                                 }
170                                 if ( isElement && ( name === "height" || name === "width" ) ) {
171                                         // Make sure that nothing sneaks out
172                                         // Record all 3 overflow attributes because IE does not
173                                         // change the overflow attribute when overflowX and
174                                         // overflowY are set to the same value
175                                         opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
177                                         // Set display property to inline-block for height/width
178                                         // animations on inline elements that are having width/height
179                                         // animated
180                                         if ( jQuery.css( this, "display" ) === "inline" &&
181                                                         jQuery.css( this, "float" ) === "none" ) {
182                                                 if ( !jQuery.support.inlineBlockNeedsLayout ) {
183                                                         this.style.display = "inline-block";
185                                                 } else {
186                                                         display = defaultDisplay( this.nodeName );
188                                                         // inline-level elements accept inline-block;
189                                                         // block-level elements need to be inline with layout
190                                                         if ( display === "inline" ) {
191                                                                 this.style.display = "inline-block";
193                                                         } else {
194                                                                 this.style.display = "inline";
195                                                                 this.style.zoom = 1;
196                                                         }
197                                                 }
198                                         }
199                                 }
200                         }
202                         if ( opt.overflow != null ) {
203                                 this.style.overflow = "hidden";
204                         }
206                         for ( p in prop ) {
207                                 e = new jQuery.fx( this, opt, p );
208                                 val = prop[ p ];
210                                 if ( rfxtypes.test(val) ) {
211                                         e[ val === "toggle" ? hidden ? "show" : "hide" : val ]();
213                                 } else {
214                                         parts = rfxnum.exec( val );
215                                         start = e.cur();
217                                         if ( parts ) {
218                                                 end = parseFloat( parts[2] );
219                                                 unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
221                                                 // We need to compute starting value
222                                                 if ( unit !== "px" ) {
223                                                         jQuery.style( this, p, (end || 1) + unit);
224                                                         start = ((end || 1) / e.cur()) * start;
225                                                         jQuery.style( this, p, start + unit);
226                                                 }
228                                                 // If a +=/-= token was provided, we're doing a relative animation
229                                                 if ( parts[1] ) {
230                                                         end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
231                                                 }
233                                                 e.custom( start, end, unit );
235                                         } else {
236                                                 e.custom( start, val, "" );
237                                         }
238                                 }
239                         }
241                         // For JS strict compliance
242                         return true;
243                 });
244         },
246         stop: function( clearQueue, gotoEnd ) {
247                 if ( clearQueue ) {
248                         this.queue([]);
249                 }
251                 this.each(function() {
252                         var timers = jQuery.timers,
253                                 i = timers.length;
254                         // clear marker counters if we know they won't be
255                         if ( !gotoEnd ) {
256                                 jQuery._unmark( true, this );
257                         }
258                         while ( i-- ) {
259                                 if ( timers[i].elem === this ) {
260                                         if (gotoEnd) {
261                                                 // force the next step to be the last
262                                                 timers[i](true);
263                                         }
265                                         timers.splice(i, 1);
266                                 }
267                         }
268                 });
270                 // start the next in the queue if the last step wasn't forced
271                 if ( !gotoEnd ) {
272                         this.dequeue();
273                 }
275                 return this;
276         }
280 // Animations created synchronously will run synchronously
281 function createFxNow() {
282         setTimeout( clearFxNow, 0 );
283         return ( fxNow = jQuery.now() );
286 function clearFxNow() {
287         fxNow = undefined;
290 // Generate parameters to create a standard animation
291 function genFx( type, num ) {
292         var obj = {};
294         jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
295                 obj[ this ] = type;
296         });
298         return obj;
301 // Generate shortcuts for custom animations
302 jQuery.each({
303         slideDown: genFx("show", 1),
304         slideUp: genFx("hide", 1),
305         slideToggle: genFx("toggle", 1),
306         fadeIn: { opacity: "show" },
307         fadeOut: { opacity: "hide" },
308         fadeToggle: { opacity: "toggle" }
309 }, function( name, props ) {
310         jQuery.fn[ name ] = function( speed, easing, callback ) {
311                 return this.animate( props, speed, easing, callback );
312         };
315 jQuery.extend({
316         speed: function( speed, easing, fn ) {
317                 var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
318                         complete: fn || !fn && easing ||
319                                 jQuery.isFunction( speed ) && speed,
320                         duration: speed,
321                         easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
322                 };
324                 opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
325                         opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
327                 // Queueing
328                 opt.old = opt.complete;
329                 opt.complete = function( noUnmark ) {
330                         if ( jQuery.isFunction( opt.old ) ) {
331                                 opt.old.call( this );
332                         }
334                         if ( opt.queue !== false ) {
335                                 jQuery.dequeue( this );
336                         } else if ( noUnmark !== false ) {
337                                 jQuery._unmark( this );
338                         }
339                 };
341                 return opt;
342         },
344         easing: {
345                 linear: function( p, n, firstNum, diff ) {
346                         return firstNum + diff * p;
347                 },
348                 swing: function( p, n, firstNum, diff ) {
349                         return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
350                 }
351         },
353         timers: [],
355         fx: function( elem, options, prop ) {
356                 this.options = options;
357                 this.elem = elem;
358                 this.prop = prop;
360                 options.orig = options.orig || {};
361         }
365 jQuery.fx.prototype = {
366         // Simple function for setting a style value
367         update: function() {
368                 if ( this.options.step ) {
369                         this.options.step.call( this.elem, this.now, this );
370                 }
372                 (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
373         },
375         // Get the current size
376         cur: function() {
377                 if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
378                         return this.elem[ this.prop ];
379                 }
381                 var parsed,
382                         r = jQuery.css( this.elem, this.prop );
383                 // Empty strings, null, undefined and "auto" are converted to 0,
384                 // complex values such as "rotate(1rad)" are returned as is,
385                 // simple values such as "10px" are parsed to Float.
386                 return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
387         },
389         // Start an animation from one number to another
390         custom: function( from, to, unit ) {
391                 var self = this,
392                         fx = jQuery.fx;
394                 this.startTime = fxNow || createFxNow();
395                 this.start = from;
396                 this.end = to;
397                 this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
398                 this.now = this.start;
399                 this.pos = this.state = 0;
401                 function t( gotoEnd ) {
402                         return self.step(gotoEnd);
403                 }
405                 t.elem = this.elem;
407                 if ( t() && jQuery.timers.push(t) && !timerId ) {
408                         timerId = setInterval( fx.tick, fx.interval );
409                 }
410         },
412         // Simple 'show' function
413         show: function() {
414                 // Remember where we started, so that we can go back to it later
415                 this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
416                 this.options.show = true;
418                 // Begin the animation
419                 // Make sure that we start at a small width/height to avoid any
420                 // flash of content
421                 this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
423                 // Start by showing the element
424                 jQuery( this.elem ).show();
425         },
427         // Simple 'hide' function
428         hide: function() {
429                 // Remember where we started, so that we can go back to it later
430                 this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
431                 this.options.hide = true;
433                 // Begin the animation
434                 this.custom(this.cur(), 0);
435         },
437         // Each step of an animation
438         step: function( gotoEnd ) {
439                 var t = fxNow || createFxNow(),
440                         done = true,
441                         elem = this.elem,
442                         options = this.options,
443                         i, n;
445                 if ( gotoEnd || t >= options.duration + this.startTime ) {
446                         this.now = this.end;
447                         this.pos = this.state = 1;
448                         this.update();
450                         options.animatedProperties[ this.prop ] = true;
452                         for ( i in options.animatedProperties ) {
453                                 if ( options.animatedProperties[i] !== true ) {
454                                         done = false;
455                                 }
456                         }
458                         if ( done ) {
459                                 // Reset the overflow
460                                 if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
462                                         jQuery.each( [ "", "X", "Y" ], function (index, value) {
463                                                 elem.style[ "overflow" + value ] = options.overflow[index];
464                                         });
465                                 }
467                                 // Hide the element if the "hide" operation was done
468                                 if ( options.hide ) {
469                                         jQuery(elem).hide();
470                                 }
472                                 // Reset the properties, if the item has been hidden or shown
473                                 if ( options.hide || options.show ) {
474                                         for ( var p in options.animatedProperties ) {
475                                                 jQuery.style( elem, p, options.orig[p] );
476                                         }
477                                 }
479                                 // Execute the complete function
480                                 options.complete.call( elem );
481                         }
483                         return false;
485                 } else {
486                         // classical easing cannot be used with an Infinity duration
487                         if ( options.duration == Infinity ) {
488                                 this.now = t;
489                         } else {
490                                 n = t - this.startTime;
491                                 this.state = n / options.duration;
493                                 // Perform the easing function, defaults to swing
494                                 this.pos = jQuery.easing[ options.animatedProperties[ this.prop ] ]( this.state, n, 0, 1, options.duration );
495                                 this.now = this.start + ((this.end - this.start) * this.pos);
496                         }
497                         // Perform the next step of the animation
498                         this.update();
499                 }
501                 return true;
502         }
505 jQuery.extend( jQuery.fx, {
506         tick: function() {
507                 for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) {
508                         if ( !timers[i]() ) {
509                                 timers.splice(i--, 1);
510                         }
511                 }
513                 if ( !timers.length ) {
514                         jQuery.fx.stop();
515                 }
516         },
518         interval: 13,
520         stop: function() {
521                 clearInterval( timerId );
522                 timerId = null;
523         },
525         speeds: {
526                 slow: 600,
527                 fast: 200,
528                 // Default speed
529                 _default: 400
530         },
532         step: {
533                 opacity: function( fx ) {
534                         jQuery.style( fx.elem, "opacity", fx.now );
535                 },
537                 _default: function( fx ) {
538                         if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
539                                 fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
540                         } else {
541                                 fx.elem[ fx.prop ] = fx.now;
542                         }
543                 }
544         }
547 if ( jQuery.expr && jQuery.expr.filters ) {
548         jQuery.expr.filters.animated = function( elem ) {
549                 return jQuery.grep(jQuery.timers, function( fn ) {
550                         return elem === fn.elem;
551                 }).length;
552         };
555 // Try to restore the default display value of an element
556 function defaultDisplay( nodeName ) {
558         if ( !elemdisplay[ nodeName ] ) {
560                 var body = document.body,
561                         elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
562                         display = elem.css( "display" );
564                 elem.remove();
566                 // If the simple way fails,
567                 // get element's real default display by attaching it to a temp iframe
568                 if ( display === "none" || display === "" ) {
569                         // No iframe to use yet, so create it
570                         if ( !iframe ) {
571                                 iframe = document.createElement( "iframe" );
572                                 iframe.frameBorder = iframe.width = iframe.height = 0;
573                         }
575                         body.appendChild( iframe );
577                         // Create a cacheable copy of the iframe document on first call.
578                         // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
579                         // document to it; WebKit & Firefox won't allow reusing the iframe document.
580                         if ( !iframeDoc || !iframe.createElement ) {
581                                 iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
582                                 iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
583                                 iframeDoc.close();
584                         }
586                         elem = iframeDoc.createElement( nodeName );
588                         iframeDoc.body.appendChild( elem );
590                         display = jQuery.css( elem, "display" );
592                         body.removeChild( iframe );
593                 }
595                 // Store the correct default display
596                 elemdisplay[ nodeName ] = display;
597         }
599         return elemdisplay[ nodeName ];
602 })( jQuery );