Offset: allow offset setter to throw for disconnected elements
[jquery.git] / src / effects.js
blobe19b04b065134764e49906955a4823313a87b71c
1 define([
2         "./core",
3         "./var/document",
4         "./var/rcssNum",
5         "./css/var/cssExpand",
6         "./css/var/isHidden",
7         "./css/adjustCSS",
8         "./css/defaultDisplay",
9         "./data/var/dataPriv",
11         "./core/init",
12         "./effects/Tween",
13         "./queue",
14         "./css",
15         "./deferred",
16         "./traversing"
17 ], function( jQuery, document, rcssNum, cssExpand,
18         isHidden, adjustCSS, defaultDisplay, dataPriv ) {
20 var
21         fxNow, timerId,
22         rfxtypes = /^(?:toggle|show|hide)$/,
23         rrun = /queueHooks$/,
24         animationPrefilters = [ defaultPrefilter ],
25         tweeners = {
26                 "*": [ function( prop, value ) {
27                         var tween = this.createTween( prop, value );
28                         adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
29                         return tween;
30                 } ]
31         };
33 function raf() {
34         if ( timerId ) {
35                 window.requestAnimationFrame( raf );
36                 jQuery.fx.tick();
37         }
40 // Animations created synchronously will run synchronously
41 function createFxNow() {
42         setTimeout(function() {
43                 fxNow = undefined;
44         });
45         return ( fxNow = jQuery.now() );
48 // Generate parameters to create a standard animation
49 function genFx( type, includeWidth ) {
50         var which,
51                 i = 0,
52                 attrs = { height: type };
54         // If we include width, step value is 1 to do all cssExpand values,
55         // otherwise step value is 2 to skip over Left and Right
56         includeWidth = includeWidth ? 1 : 0;
57         for ( ; i < 4 ; i += 2 - includeWidth ) {
58                 which = cssExpand[ i ];
59                 attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
60         }
62         if ( includeWidth ) {
63                 attrs.opacity = attrs.width = type;
64         }
66         return attrs;
69 function createTween( value, prop, animation ) {
70         var tween,
71                 collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
72                 index = 0,
73                 length = collection.length;
74         for ( ; index < length; index++ ) {
75                 if ( (tween = collection[ index ].call( animation, prop, value )) ) {
77                         // We're done with this property
78                         return tween;
79                 }
80         }
83 function defaultPrefilter( elem, props, opts ) {
84         /* jshint validthis: true */
85         var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
86                 anim = this,
87                 orig = {},
88                 style = elem.style,
89                 hidden = elem.nodeType && isHidden( elem ),
90                 dataShow = dataPriv.get( elem, "fxshow" );
92         // Handle queue: false promises
93         if ( !opts.queue ) {
94                 hooks = jQuery._queueHooks( elem, "fx" );
95                 if ( hooks.unqueued == null ) {
96                         hooks.unqueued = 0;
97                         oldfire = hooks.empty.fire;
98                         hooks.empty.fire = function() {
99                                 if ( !hooks.unqueued ) {
100                                         oldfire();
101                                 }
102                         };
103                 }
104                 hooks.unqueued++;
106                 anim.always(function() {
107                         // Ensure the complete handler is called before this completes
108                         anim.always(function() {
109                                 hooks.unqueued--;
110                                 if ( !jQuery.queue( elem, "fx" ).length ) {
111                                         hooks.empty.fire();
112                                 }
113                         });
114                 });
115         }
117         // Height/width overflow pass
118         if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
119                 // Make sure that nothing sneaks out
120                 // Record all 3 overflow attributes because IE9-10 do not
121                 // change the overflow attribute when overflowX and
122                 // overflowY are set to the same value
123                 opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
125                 // Set display property to inline-block for height/width
126                 // animations on inline elements that are having width/height animated
127                 display = jQuery.css( elem, "display" );
129                 // Test default display if display is currently "none"
130                 checkDisplay = display === "none" ?
131                         dataPriv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;
133                 if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
134                         style.display = "inline-block";
135                 }
136         }
138         if ( opts.overflow ) {
139                 style.overflow = "hidden";
140                 anim.always(function() {
141                         style.overflow = opts.overflow[ 0 ];
142                         style.overflowX = opts.overflow[ 1 ];
143                         style.overflowY = opts.overflow[ 2 ];
144                 });
145         }
147         // show/hide pass
148         for ( prop in props ) {
149                 value = props[ prop ];
150                 if ( rfxtypes.exec( value ) ) {
151                         delete props[ prop ];
152                         toggle = toggle || value === "toggle";
153                         if ( value === ( hidden ? "hide" : "show" ) ) {
155                                 // If there is dataShow left over from a stopped hide or show
156                                 // and we are going to proceed with show, we should pretend to be hidden
157                                 if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
158                                         hidden = true;
159                                 } else {
160                                         continue;
161                                 }
162                         }
163                         orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
165                 // Any non-fx value stops us from restoring the original display value
166                 } else {
167                         display = undefined;
168                 }
169         }
171         if ( !jQuery.isEmptyObject( orig ) ) {
172                 if ( dataShow ) {
173                         if ( "hidden" in dataShow ) {
174                                 hidden = dataShow.hidden;
175                         }
176                 } else {
177                         dataShow = dataPriv.access( elem, "fxshow", {} );
178                 }
180                 // Store state if its toggle - enables .stop().toggle() to "reverse"
181                 if ( toggle ) {
182                         dataShow.hidden = !hidden;
183                 }
184                 if ( hidden ) {
185                         jQuery( elem ).show();
186                 } else {
187                         anim.done(function() {
188                                 jQuery( elem ).hide();
189                         });
190                 }
191                 anim.done(function() {
192                         var prop;
194                         dataPriv.remove( elem, "fxshow" );
195                         for ( prop in orig ) {
196                                 jQuery.style( elem, prop, orig[ prop ] );
197                         }
198                 });
199                 for ( prop in orig ) {
200                         tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
202                         if ( !( prop in dataShow ) ) {
203                                 dataShow[ prop ] = tween.start;
204                                 if ( hidden ) {
205                                         tween.end = tween.start;
206                                         tween.start = prop === "width" || prop === "height" ? 1 : 0;
207                                 }
208                         }
209                 }
211         // If this is a noop like .hide().hide(), restore an overwritten display value
212         } else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) {
213                 style.display = display;
214         }
217 function propFilter( props, specialEasing ) {
218         var index, name, easing, value, hooks;
220         // camelCase, specialEasing and expand cssHook pass
221         for ( index in props ) {
222                 name = jQuery.camelCase( index );
223                 easing = specialEasing[ name ];
224                 value = props[ index ];
225                 if ( jQuery.isArray( value ) ) {
226                         easing = value[ 1 ];
227                         value = props[ index ] = value[ 0 ];
228                 }
230                 if ( index !== name ) {
231                         props[ name ] = value;
232                         delete props[ index ];
233                 }
235                 hooks = jQuery.cssHooks[ name ];
236                 if ( hooks && "expand" in hooks ) {
237                         value = hooks.expand( value );
238                         delete props[ name ];
240                         // Not quite $.extend, this won't overwrite existing keys.
241                         // Reusing 'index' because we have the correct "name"
242                         for ( index in value ) {
243                                 if ( !( index in props ) ) {
244                                         props[ index ] = value[ index ];
245                                         specialEasing[ index ] = easing;
246                                 }
247                         }
248                 } else {
249                         specialEasing[ name ] = easing;
250                 }
251         }
254 function Animation( elem, properties, options ) {
255         var result,
256                 stopped,
257                 index = 0,
258                 length = animationPrefilters.length,
259                 deferred = jQuery.Deferred().always( function() {
260                         // Don't match elem in the :animated selector
261                         delete tick.elem;
262                 }),
263                 tick = function() {
264                         if ( stopped ) {
265                                 return false;
266                         }
267                         var currentTime = fxNow || createFxNow(),
268                                 remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
269                                 // Support: Android 2.3
270                                 // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
271                                 temp = remaining / animation.duration || 0,
272                                 percent = 1 - temp,
273                                 index = 0,
274                                 length = animation.tweens.length;
276                         for ( ; index < length ; index++ ) {
277                                 animation.tweens[ index ].run( percent );
278                         }
280                         deferred.notifyWith( elem, [ animation, percent, remaining ]);
282                         if ( percent < 1 && length ) {
283                                 return remaining;
284                         } else {
285                                 deferred.resolveWith( elem, [ animation ] );
286                                 return false;
287                         }
288                 },
289                 animation = deferred.promise({
290                         elem: elem,
291                         props: jQuery.extend( {}, properties ),
292                         opts: jQuery.extend( true, { specialEasing: {} }, options ),
293                         originalProperties: properties,
294                         originalOptions: options,
295                         startTime: fxNow || createFxNow(),
296                         duration: options.duration,
297                         tweens: [],
298                         createTween: function( prop, end ) {
299                                 var tween = jQuery.Tween( elem, animation.opts, prop, end,
300                                                 animation.opts.specialEasing[ prop ] || animation.opts.easing );
301                                 animation.tweens.push( tween );
302                                 return tween;
303                         },
304                         stop: function( gotoEnd ) {
305                                 var index = 0,
306                                         // If we are going to the end, we want to run all the tweens
307                                         // otherwise we skip this part
308                                         length = gotoEnd ? animation.tweens.length : 0;
309                                 if ( stopped ) {
310                                         return this;
311                                 }
312                                 stopped = true;
313                                 for ( ; index < length ; index++ ) {
314                                         animation.tweens[ index ].run( 1 );
315                                 }
317                                 // Resolve when we played the last frame; otherwise, reject
318                                 if ( gotoEnd ) {
319                                         deferred.resolveWith( elem, [ animation, gotoEnd ] );
320                                 } else {
321                                         deferred.rejectWith( elem, [ animation, gotoEnd ] );
322                                 }
323                                 return this;
324                         }
325                 }),
326                 props = animation.props;
328         propFilter( props, animation.opts.specialEasing );
330         for ( ; index < length ; index++ ) {
331                 result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
332                 if ( result ) {
333                         return result;
334                 }
335         }
337         jQuery.map( props, createTween, animation );
339         if ( jQuery.isFunction( animation.opts.start ) ) {
340                 animation.opts.start.call( elem, animation );
341         }
343         jQuery.fx.timer(
344                 jQuery.extend( tick, {
345                         elem: elem,
346                         anim: animation,
347                         queue: animation.opts.queue
348                 })
349         );
351         // attach callbacks from options
352         return animation.progress( animation.opts.progress )
353                 .done( animation.opts.done, animation.opts.complete )
354                 .fail( animation.opts.fail )
355                 .always( animation.opts.always );
358 jQuery.Animation = jQuery.extend( Animation, {
360         tweener: function( props, callback ) {
361                 if ( jQuery.isFunction( props ) ) {
362                         callback = props;
363                         props = [ "*" ];
364                 } else {
365                         props = props.split(" ");
366                 }
368                 var prop,
369                         index = 0,
370                         length = props.length;
372                 for ( ; index < length ; index++ ) {
373                         prop = props[ index ];
374                         tweeners[ prop ] = tweeners[ prop ] || [];
375                         tweeners[ prop ].unshift( callback );
376                 }
377         },
379         prefilter: function( callback, prepend ) {
380                 if ( prepend ) {
381                         animationPrefilters.unshift( callback );
382                 } else {
383                         animationPrefilters.push( callback );
384                 }
385         }
388 jQuery.speed = function( speed, easing, fn ) {
389         var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
390                 complete: fn || !fn && easing ||
391                         jQuery.isFunction( speed ) && speed,
392                 duration: speed,
393                 easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
394         };
396         // Go to the end state if fx are off or if document is hidden
397         if ( jQuery.fx.off || document.hidden ) {
398                 opt.duration = 0;
400         } else {
401                 opt.duration = typeof opt.duration === "number" ?
402                         opt.duration : opt.duration in jQuery.fx.speeds ?
403                                 jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
404         }
406         // Normalize opt.queue - true/undefined/null -> "fx"
407         if ( opt.queue == null || opt.queue === true ) {
408                 opt.queue = "fx";
409         }
411         // Queueing
412         opt.old = opt.complete;
414         opt.complete = function() {
415                 if ( jQuery.isFunction( opt.old ) ) {
416                         opt.old.call( this );
417                 }
419                 if ( opt.queue ) {
420                         jQuery.dequeue( this, opt.queue );
421                 }
422         };
424         return opt;
427 jQuery.fn.extend({
428         fadeTo: function( speed, to, easing, callback ) {
430                 // Show any hidden elements after setting opacity to 0
431                 return this.filter( isHidden ).css( "opacity", 0 ).show()
433                         // Animate to the value specified
434                         .end().animate({ opacity: to }, speed, easing, callback );
435         },
436         animate: function( prop, speed, easing, callback ) {
437                 var empty = jQuery.isEmptyObject( prop ),
438                         optall = jQuery.speed( speed, easing, callback ),
439                         doAnimation = function() {
440                                 // Operate on a copy of prop so per-property easing won't be lost
441                                 var anim = Animation( this, jQuery.extend( {}, prop ), optall );
443                                 // Empty animations, or finishing resolves immediately
444                                 if ( empty || dataPriv.get( this, "finish" ) ) {
445                                         anim.stop( true );
446                                 }
447                         };
448                         doAnimation.finish = doAnimation;
450                 return empty || optall.queue === false ?
451                         this.each( doAnimation ) :
452                         this.queue( optall.queue, doAnimation );
453         },
454         stop: function( type, clearQueue, gotoEnd ) {
455                 var stopQueue = function( hooks ) {
456                         var stop = hooks.stop;
457                         delete hooks.stop;
458                         stop( gotoEnd );
459                 };
461                 if ( typeof type !== "string" ) {
462                         gotoEnd = clearQueue;
463                         clearQueue = type;
464                         type = undefined;
465                 }
466                 if ( clearQueue && type !== false ) {
467                         this.queue( type || "fx", [] );
468                 }
470                 return this.each(function() {
471                         var dequeue = true,
472                                 index = type != null && type + "queueHooks",
473                                 timers = jQuery.timers,
474                                 data = dataPriv.get( this );
476                         if ( index ) {
477                                 if ( data[ index ] && data[ index ].stop ) {
478                                         stopQueue( data[ index ] );
479                                 }
480                         } else {
481                                 for ( index in data ) {
482                                         if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
483                                                 stopQueue( data[ index ] );
484                                         }
485                                 }
486                         }
488                         for ( index = timers.length; index--; ) {
489                                 if ( timers[ index ].elem === this &&
490                                         (type == null || timers[ index ].queue === type) ) {
492                                         timers[ index ].anim.stop( gotoEnd );
493                                         dequeue = false;
494                                         timers.splice( index, 1 );
495                                 }
496                         }
498                         // Start the next in the queue if the last step wasn't forced.
499                         // Timers currently will call their complete callbacks, which
500                         // will dequeue but only if they were gotoEnd.
501                         if ( dequeue || !gotoEnd ) {
502                                 jQuery.dequeue( this, type );
503                         }
504                 });
505         },
506         finish: function( type ) {
507                 if ( type !== false ) {
508                         type = type || "fx";
509                 }
510                 return this.each(function() {
511                         var index,
512                                 data = dataPriv.get( this ),
513                                 queue = data[ type + "queue" ],
514                                 hooks = data[ type + "queueHooks" ],
515                                 timers = jQuery.timers,
516                                 length = queue ? queue.length : 0;
518                         // Enable finishing flag on private data
519                         data.finish = true;
521                         // Empty the queue first
522                         jQuery.queue( this, type, [] );
524                         if ( hooks && hooks.stop ) {
525                                 hooks.stop.call( this, true );
526                         }
528                         // Look for any active animations, and finish them
529                         for ( index = timers.length; index--; ) {
530                                 if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
531                                         timers[ index ].anim.stop( true );
532                                         timers.splice( index, 1 );
533                                 }
534                         }
536                         // Look for any animations in the old queue and finish them
537                         for ( index = 0; index < length; index++ ) {
538                                 if ( queue[ index ] && queue[ index ].finish ) {
539                                         queue[ index ].finish.call( this );
540                                 }
541                         }
543                         // Turn off finishing flag
544                         delete data.finish;
545                 });
546         }
549 jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
550         var cssFn = jQuery.fn[ name ];
551         jQuery.fn[ name ] = function( speed, easing, callback ) {
552                 return speed == null || typeof speed === "boolean" ?
553                         cssFn.apply( this, arguments ) :
554                         this.animate( genFx( name, true ), speed, easing, callback );
555         };
558 // Generate shortcuts for custom animations
559 jQuery.each({
560         slideDown: genFx("show"),
561         slideUp: genFx("hide"),
562         slideToggle: genFx("toggle"),
563         fadeIn: { opacity: "show" },
564         fadeOut: { opacity: "hide" },
565         fadeToggle: { opacity: "toggle" }
566 }, function( name, props ) {
567         jQuery.fn[ name ] = function( speed, easing, callback ) {
568                 return this.animate( props, speed, easing, callback );
569         };
572 jQuery.timers = [];
573 jQuery.fx.tick = function() {
574         var timer,
575                 i = 0,
576                 timers = jQuery.timers;
578         fxNow = jQuery.now();
580         for ( ; i < timers.length; i++ ) {
581                 timer = timers[ i ];
582                 // Checks the timer has not already been removed
583                 if ( !timer() && timers[ i ] === timer ) {
584                         timers.splice( i--, 1 );
585                 }
586         }
588         if ( !timers.length ) {
589                 jQuery.fx.stop();
590         }
591         fxNow = undefined;
594 jQuery.fx.timer = function( timer ) {
595         jQuery.timers.push( timer );
596         if ( timer() ) {
597                 jQuery.fx.start();
598         } else {
599                 jQuery.timers.pop();
600         }
603 jQuery.fx.interval = 13;
604 jQuery.fx.start = function() {
605         if ( !timerId ) {
606                 timerId = window.requestAnimationFrame ?
607                         window.requestAnimationFrame( raf ) :
608                         setInterval( jQuery.fx.tick, jQuery.fx.interval );
609         }
612 jQuery.fx.stop = function() {
613         if ( window.cancelAnimationFrame ) {
614                 window.cancelAnimationFrame( timerId );
615         } else {
616                 clearInterval( timerId );
617         }
619         timerId = null;
622 jQuery.fx.speeds = {
623         slow: 600,
624         fast: 200,
625         // Default speed
626         _default: 400
629 return jQuery;