Update copyright year
[jquery.git] / src / effects.js
blob5cac7d2d8c6e23cf5c67fb9b13bc7b73138fb31f
1 var fxNow, timerId,
2         rfxtypes = /^(?:toggle|show|hide)$/,
3         rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
4         rrun = /queueHooks$/,
5         animationPrefilters = [ defaultPrefilter ],
6         tweeners = {
7                 "*": [function( prop, value ) {
8                         var end, unit,
9                                 tween = this.createTween( prop, value ),
10                                 parts = rfxnum.exec( value ),
11                                 target = tween.cur(),
12                                 start = +target || 0,
13                                 scale = 1,
14                                 maxIterations = 20;
16                         if ( parts ) {
17                                 end = +parts[2];
18                                 unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
20                                 // We need to compute starting value
21                                 if ( unit !== "px" && start ) {
22                                         // Iteratively approximate from a nonzero starting point
23                                         // Prefer the current property, because this process will be trivial if it uses the same units
24                                         // Fallback to end or a simple constant
25                                         start = jQuery.css( tween.elem, prop, true ) || end || 1;
27                                         do {
28                                                 // If previous iteration zeroed out, double until we get *something*
29                                                 // Use a string for doubling factor so we don't accidentally see scale as unchanged below
30                                                 scale = scale || ".5";
32                                                 // Adjust and apply
33                                                 start = start / scale;
34                                                 jQuery.style( tween.elem, prop, start + unit );
36                                         // Update scale, tolerating zero or NaN from tween.cur()
37                                         // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
38                                         } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
39                                 }
41                                 tween.unit = unit;
42                                 tween.start = start;
43                                 // If a +=/-= token was provided, we're doing a relative animation
44                                 tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
45                         }
46                         return tween;
47                 }]
48         };
50 // Animations created synchronously will run synchronously
51 function createFxNow() {
52         setTimeout(function() {
53                 fxNow = undefined;
54         });
55         return ( fxNow = jQuery.now() );
58 function createTweens( animation, props ) {
59         jQuery.each( props, function( prop, value ) {
60                 var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
61                         index = 0,
62                         length = collection.length;
63                 for ( ; index < length; index++ ) {
64                         if ( collection[ index ].call( animation, prop, value ) ) {
66                                 // we're done with this property
67                                 return;
68                         }
69                 }
70         });
73 function Animation( elem, properties, options ) {
74         var result,
75                 stopped,
76                 index = 0,
77                 length = animationPrefilters.length,
78                 deferred = jQuery.Deferred().always( function() {
79                         // don't match elem in the :animated selector
80                         delete tick.elem;
81                 }),
82                 tick = function() {
83                         if ( stopped ) {
84                                 return false;
85                         }
86                         var currentTime = fxNow || createFxNow(),
87                                 remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
88                                 // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
89                                 temp = remaining / animation.duration || 0,
90                                 percent = 1 - temp,
91                                 index = 0,
92                                 length = animation.tweens.length;
94                         for ( ; index < length ; index++ ) {
95                                 animation.tweens[ index ].run( percent );
96                         }
98                         deferred.notifyWith( elem, [ animation, percent, remaining ]);
100                         if ( percent < 1 && length ) {
101                                 return remaining;
102                         } else {
103                                 deferred.resolveWith( elem, [ animation ] );
104                                 return false;
105                         }
106                 },
107                 animation = deferred.promise({
108                         elem: elem,
109                         props: jQuery.extend( {}, properties ),
110                         opts: jQuery.extend( true, { specialEasing: {} }, options ),
111                         originalProperties: properties,
112                         originalOptions: options,
113                         startTime: fxNow || createFxNow(),
114                         duration: options.duration,
115                         tweens: [],
116                         createTween: function( prop, end ) {
117                                 var tween = jQuery.Tween( elem, animation.opts, prop, end,
118                                                 animation.opts.specialEasing[ prop ] || animation.opts.easing );
119                                 animation.tweens.push( tween );
120                                 return tween;
121                         },
122                         stop: function( gotoEnd ) {
123                                 var index = 0,
124                                         // if we are going to the end, we want to run all the tweens
125                                         // otherwise we skip this part
126                                         length = gotoEnd ? animation.tweens.length : 0;
127                                 if ( stopped ) {
128                                         return this;
129                                 }
130                                 stopped = true;
131                                 for ( ; index < length ; index++ ) {
132                                         animation.tweens[ index ].run( 1 );
133                                 }
135                                 // resolve when we played the last frame
136                                 // otherwise, reject
137                                 if ( gotoEnd ) {
138                                         deferred.resolveWith( elem, [ animation, gotoEnd ] );
139                                 } else {
140                                         deferred.rejectWith( elem, [ animation, gotoEnd ] );
141                                 }
142                                 return this;
143                         }
144                 }),
145                 props = animation.props;
147         propFilter( props, animation.opts.specialEasing );
149         for ( ; index < length ; index++ ) {
150                 result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
151                 if ( result ) {
152                         return result;
153                 }
154         }
156         createTweens( animation, props );
158         if ( jQuery.isFunction( animation.opts.start ) ) {
159                 animation.opts.start.call( elem, animation );
160         }
162         jQuery.fx.timer(
163                 jQuery.extend( tick, {
164                         elem: elem,
165                         anim: animation,
166                         queue: animation.opts.queue
167                 })
168         );
170         // attach callbacks from options
171         return animation.progress( animation.opts.progress )
172                 .done( animation.opts.done, animation.opts.complete )
173                 .fail( animation.opts.fail )
174                 .always( animation.opts.always );
177 function propFilter( props, specialEasing ) {
178         var value, name, index, easing, hooks;
180         // camelCase, specialEasing and expand cssHook pass
181         for ( index in props ) {
182                 name = jQuery.camelCase( index );
183                 easing = specialEasing[ name ];
184                 value = props[ index ];
185                 if ( jQuery.isArray( value ) ) {
186                         easing = value[ 1 ];
187                         value = props[ index ] = value[ 0 ];
188                 }
190                 if ( index !== name ) {
191                         props[ name ] = value;
192                         delete props[ index ];
193                 }
195                 hooks = jQuery.cssHooks[ name ];
196                 if ( hooks && "expand" in hooks ) {
197                         value = hooks.expand( value );
198                         delete props[ name ];
200                         // not quite $.extend, this wont overwrite keys already present.
201                         // also - reusing 'index' from above because we have the correct "name"
202                         for ( index in value ) {
203                                 if ( !( index in props ) ) {
204                                         props[ index ] = value[ index ];
205                                         specialEasing[ index ] = easing;
206                                 }
207                         }
208                 } else {
209                         specialEasing[ name ] = easing;
210                 }
211         }
214 jQuery.Animation = jQuery.extend( Animation, {
216         tweener: function( props, callback ) {
217                 if ( jQuery.isFunction( props ) ) {
218                         callback = props;
219                         props = [ "*" ];
220                 } else {
221                         props = props.split(" ");
222                 }
224                 var prop,
225                         index = 0,
226                         length = props.length;
228                 for ( ; index < length ; index++ ) {
229                         prop = props[ index ];
230                         tweeners[ prop ] = tweeners[ prop ] || [];
231                         tweeners[ prop ].unshift( callback );
232                 }
233         },
235         prefilter: function( callback, prepend ) {
236                 if ( prepend ) {
237                         animationPrefilters.unshift( callback );
238                 } else {
239                         animationPrefilters.push( callback );
240                 }
241         }
244 function defaultPrefilter( elem, props, opts ) {
245         /*jshint validthis:true */
246         var prop, index, length,
247                 value, dataShow, toggle,
248                 tween, hooks, oldfire,
249                 anim = this,
250                 style = elem.style,
251                 orig = {},
252                 handled = [],
253                 hidden = elem.nodeType && isHidden( elem );
255         // handle queue: false promises
256         if ( !opts.queue ) {
257                 hooks = jQuery._queueHooks( elem, "fx" );
258                 if ( hooks.unqueued == null ) {
259                         hooks.unqueued = 0;
260                         oldfire = hooks.empty.fire;
261                         hooks.empty.fire = function() {
262                                 if ( !hooks.unqueued ) {
263                                         oldfire();
264                                 }
265                         };
266                 }
267                 hooks.unqueued++;
269                 anim.always(function() {
270                         // doing this makes sure that the complete handler will be called
271                         // before this completes
272                         anim.always(function() {
273                                 hooks.unqueued--;
274                                 if ( !jQuery.queue( elem, "fx" ).length ) {
275                                         hooks.empty.fire();
276                                 }
277                         });
278                 });
279         }
281         // height/width overflow pass
282         if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
283                 // Make sure that nothing sneaks out
284                 // Record all 3 overflow attributes because IE does not
285                 // change the overflow attribute when overflowX and
286                 // overflowY are set to the same value
287                 opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
289                 // Set display property to inline-block for height/width
290                 // animations on inline elements that are having width/height animated
291                 if ( jQuery.css( elem, "display" ) === "inline" &&
292                                 jQuery.css( elem, "float" ) === "none" ) {
294                         // inline-level elements accept inline-block;
295                         // block-level elements need to be inline with layout
296                         if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
297                                 style.display = "inline-block";
299                         } else {
300                                 style.zoom = 1;
301                         }
302                 }
303         }
305         if ( opts.overflow ) {
306                 style.overflow = "hidden";
307                 if ( !jQuery.support.shrinkWrapBlocks ) {
308                         anim.always(function() {
309                                 style.overflow = opts.overflow[ 0 ];
310                                 style.overflowX = opts.overflow[ 1 ];
311                                 style.overflowY = opts.overflow[ 2 ];
312                         });
313                 }
314         }
317         // show/hide pass
318         for ( index in props ) {
319                 value = props[ index ];
320                 if ( rfxtypes.exec( value ) ) {
321                         delete props[ index ];
322                         toggle = toggle || value === "toggle";
323                         if ( value === ( hidden ? "hide" : "show" ) ) {
324                                 continue;
325                         }
326                         handled.push( index );
327                 }
328         }
330         length = handled.length;
331         if ( length ) {
332                 dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
333                 if ( "hidden" in dataShow ) {
334                         hidden = dataShow.hidden;
335                 }
337                 // store state if its toggle - enables .stop().toggle() to "reverse"
338                 if ( toggle ) {
339                         dataShow.hidden = !hidden;
340                 }
341                 if ( hidden ) {
342                         jQuery( elem ).show();
343                 } else {
344                         anim.done(function() {
345                                 jQuery( elem ).hide();
346                         });
347                 }
348                 anim.done(function() {
349                         var prop;
350                         jQuery._removeData( elem, "fxshow" );
351                         for ( prop in orig ) {
352                                 jQuery.style( elem, prop, orig[ prop ] );
353                         }
354                 });
355                 for ( index = 0 ; index < length ; index++ ) {
356                         prop = handled[ index ];
357                         tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
358                         orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
360                         if ( !( prop in dataShow ) ) {
361                                 dataShow[ prop ] = tween.start;
362                                 if ( hidden ) {
363                                         tween.end = tween.start;
364                                         tween.start = prop === "width" || prop === "height" ? 1 : 0;
365                                 }
366                         }
367                 }
368         }
371 function Tween( elem, options, prop, end, easing ) {
372         return new Tween.prototype.init( elem, options, prop, end, easing );
374 jQuery.Tween = Tween;
376 Tween.prototype = {
377         constructor: Tween,
378         init: function( elem, options, prop, end, easing, unit ) {
379                 this.elem = elem;
380                 this.prop = prop;
381                 this.easing = easing || "swing";
382                 this.options = options;
383                 this.start = this.now = this.cur();
384                 this.end = end;
385                 this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
386         },
387         cur: function() {
388                 var hooks = Tween.propHooks[ this.prop ];
390                 return hooks && hooks.get ?
391                         hooks.get( this ) :
392                         Tween.propHooks._default.get( this );
393         },
394         run: function( percent ) {
395                 var eased,
396                         hooks = Tween.propHooks[ this.prop ];
398                 if ( this.options.duration ) {
399                         this.pos = eased = jQuery.easing[ this.easing ](
400                                 percent, this.options.duration * percent, 0, 1, this.options.duration
401                         );
402                 } else {
403                         this.pos = eased = percent;
404                 }
405                 this.now = ( this.end - this.start ) * eased + this.start;
407                 if ( this.options.step ) {
408                         this.options.step.call( this.elem, this.now, this );
409                 }
411                 if ( hooks && hooks.set ) {
412                         hooks.set( this );
413                 } else {
414                         Tween.propHooks._default.set( this );
415                 }
416                 return this;
417         }
420 Tween.prototype.init.prototype = Tween.prototype;
422 Tween.propHooks = {
423         _default: {
424                 get: function( tween ) {
425                         var result;
427                         if ( tween.elem[ tween.prop ] != null &&
428                                 (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
429                                 return tween.elem[ tween.prop ];
430                         }
432                         // passing an empty string as a 3rd parameter to .css will automatically
433                         // attempt a parseFloat and fallback to a string if the parse fails
434                         // so, simple values such as "10px" are parsed to Float.
435                         // complex values such as "rotate(1rad)" are returned as is.
436                         result = jQuery.css( tween.elem, tween.prop, "" );
437                         // Empty strings, null, undefined and "auto" are converted to 0.
438                         return !result || result === "auto" ? 0 : result;
439                 },
440                 set: function( tween ) {
441                         // use step hook for back compat - use cssHook if its there - use .style if its
442                         // available and use plain properties where available
443                         if ( jQuery.fx.step[ tween.prop ] ) {
444                                 jQuery.fx.step[ tween.prop ]( tween );
445                         } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
446                                 jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
447                         } else {
448                                 tween.elem[ tween.prop ] = tween.now;
449                         }
450                 }
451         }
454 // Remove in 2.0 - this supports IE8's panic based approach
455 // to setting things on disconnected nodes
457 Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
458         set: function( tween ) {
459                 if ( tween.elem.nodeType && tween.elem.parentNode ) {
460                         tween.elem[ tween.prop ] = tween.now;
461                 }
462         }
465 jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
466         var cssFn = jQuery.fn[ name ];
467         jQuery.fn[ name ] = function( speed, easing, callback ) {
468                 return speed == null || typeof speed === "boolean" ?
469                         cssFn.apply( this, arguments ) :
470                         this.animate( genFx( name, true ), speed, easing, callback );
471         };
474 jQuery.fn.extend({
475         fadeTo: function( speed, to, easing, callback ) {
477                 // show any hidden elements after setting opacity to 0
478                 return this.filter( isHidden ).css( "opacity", 0 ).show()
480                         // animate to the value specified
481                         .end().animate({ opacity: to }, speed, easing, callback );
482         },
483         animate: function( prop, speed, easing, callback ) {
484                 var empty = jQuery.isEmptyObject( prop ),
485                         optall = jQuery.speed( speed, easing, callback ),
486                         doAnimation = function() {
487                                 // Operate on a copy of prop so per-property easing won't be lost
488                                 var anim = Animation( this, jQuery.extend( {}, prop ), optall );
489                                 doAnimation.finish = function() {
490                                         anim.stop( true );
491                                 };
492                                 // Empty animations, or finishing resolves immediately
493                                 if ( empty || jQuery._data( this, "finish" ) ) {
494                                         anim.stop( true );
495                                 }
496                         };
497                         doAnimation.finish = doAnimation;
499                 return empty || optall.queue === false ?
500                         this.each( doAnimation ) :
501                         this.queue( optall.queue, doAnimation );
502         },
503         stop: function( type, clearQueue, gotoEnd ) {
504                 var stopQueue = function( hooks ) {
505                         var stop = hooks.stop;
506                         delete hooks.stop;
507                         stop( gotoEnd );
508                 };
510                 if ( typeof type !== "string" ) {
511                         gotoEnd = clearQueue;
512                         clearQueue = type;
513                         type = undefined;
514                 }
515                 if ( clearQueue && type !== false ) {
516                         this.queue( type || "fx", [] );
517                 }
519                 return this.each(function() {
520                         var dequeue = true,
521                                 index = type != null && type + "queueHooks",
522                                 timers = jQuery.timers,
523                                 data = jQuery._data( this );
525                         if ( index ) {
526                                 if ( data[ index ] && data[ index ].stop ) {
527                                         stopQueue( data[ index ] );
528                                 }
529                         } else {
530                                 for ( index in data ) {
531                                         if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
532                                                 stopQueue( data[ index ] );
533                                         }
534                                 }
535                         }
537                         for ( index = timers.length; index--; ) {
538                                 if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
539                                         timers[ index ].anim.stop( gotoEnd );
540                                         dequeue = false;
541                                         timers.splice( index, 1 );
542                                 }
543                         }
545                         // start the next in the queue if the last step wasn't forced
546                         // timers currently will call their complete callbacks, which will dequeue
547                         // but only if they were gotoEnd
548                         if ( dequeue || !gotoEnd ) {
549                                 jQuery.dequeue( this, type );
550                         }
551                 });
552         },
553         finish: function( type ) {
554                 if ( type !== false ) {
555                         type = type || "fx";
556                 }
557                 return this.each(function() {
558                         var index,
559                                 data = jQuery._data( this ),
560                                 queue = data[ type + "queue" ],
561                                 hooks = data[ type + "queueHooks" ],
562                                 timers = jQuery.timers,
563                                 length = queue ? queue.length : 0;
565                         // enable finishing flag on private data
566                         data.finish = true;
568                         // empty the queue first
569                         jQuery.queue( this, type, [] );
571                         if ( hooks && hooks.cur && hooks.cur.finish ) {
572                                 hooks.cur.finish.call( this );
573                         }
575                         // look for any active animations, and finish them
576                         for ( index = timers.length; index--; ) {
577                                 if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
578                                         timers[ index ].anim.stop( true );
579                                         timers.splice( index, 1 );
580                                 }
581                         }
583                         // look for any animations in the old queue and finish them
584                         for ( index = 0; index < length; index++ ) {
585                                 if ( queue[ index ] && queue[ index ].finish ) {
586                                         queue[ index ].finish.call( this );
587                                 }
588                         }
590                         // turn off finishing flag
591                         delete data.finish;
592                 });
593         }
596 // Generate parameters to create a standard animation
597 function genFx( type, includeWidth ) {
598         var which,
599                 attrs = { height: type },
600                 i = 0;
602         // if we include width, step value is 1 to do all cssExpand values,
603         // if we don't include width, step value is 2 to skip over Left and Right
604         includeWidth = includeWidth? 1 : 0;
605         for( ; i < 4 ; i += 2 - includeWidth ) {
606                 which = cssExpand[ i ];
607                 attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
608         }
610         if ( includeWidth ) {
611                 attrs.opacity = attrs.width = type;
612         }
614         return attrs;
617 // Generate shortcuts for custom animations
618 jQuery.each({
619         slideDown: genFx("show"),
620         slideUp: genFx("hide"),
621         slideToggle: genFx("toggle"),
622         fadeIn: { opacity: "show" },
623         fadeOut: { opacity: "hide" },
624         fadeToggle: { opacity: "toggle" }
625 }, function( name, props ) {
626         jQuery.fn[ name ] = function( speed, easing, callback ) {
627                 return this.animate( props, speed, easing, callback );
628         };
631 jQuery.speed = function( speed, easing, fn ) {
632         var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
633                 complete: fn || !fn && easing ||
634                         jQuery.isFunction( speed ) && speed,
635                 duration: speed,
636                 easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
637         };
639         opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
640                 opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
642         // normalize opt.queue - true/undefined/null -> "fx"
643         if ( opt.queue == null || opt.queue === true ) {
644                 opt.queue = "fx";
645         }
647         // Queueing
648         opt.old = opt.complete;
650         opt.complete = function() {
651                 if ( jQuery.isFunction( opt.old ) ) {
652                         opt.old.call( this );
653                 }
655                 if ( opt.queue ) {
656                         jQuery.dequeue( this, opt.queue );
657                 }
658         };
660         return opt;
663 jQuery.easing = {
664         linear: function( p ) {
665                 return p;
666         },
667         swing: function( p ) {
668                 return 0.5 - Math.cos( p*Math.PI ) / 2;
669         }
672 jQuery.timers = [];
673 jQuery.fx = Tween.prototype.init;
674 jQuery.fx.tick = function() {
675         var timer,
676                 timers = jQuery.timers,
677                 i = 0;
679         fxNow = jQuery.now();
681         for ( ; i < timers.length; i++ ) {
682                 timer = timers[ i ];
683                 // Checks the timer has not already been removed
684                 if ( !timer() && timers[ i ] === timer ) {
685                         timers.splice( i--, 1 );
686                 }
687         }
689         if ( !timers.length ) {
690                 jQuery.fx.stop();
691         }
692         fxNow = undefined;
695 jQuery.fx.timer = function( timer ) {
696         if ( timer() && jQuery.timers.push( timer ) ) {
697                 jQuery.fx.start();
698         }
701 jQuery.fx.interval = 13;
703 jQuery.fx.start = function() {
704         if ( !timerId ) {
705                 timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
706         }
709 jQuery.fx.stop = function() {
710         clearInterval( timerId );
711         timerId = null;
714 jQuery.fx.speeds = {
715         slow: 600,
716         fast: 200,
717         // Default speed
718         _default: 400
721 // Back Compat <1.8 extension point
722 jQuery.fx.step = {};
724 if ( jQuery.expr && jQuery.expr.filters ) {
725         jQuery.expr.filters.animated = function( elem ) {
726                 return jQuery.grep(jQuery.timers, function( fn ) {
727                         return elem === fn.elem;
728                 }).length;
729         };