Effects: set default easing using jQuery.easing._default
[jquery.git] / src / effects.js
blobd90a1b28e53523a7247478776e5f98a8a5ef5ab1
1 define([
2         "./core",
3         "./var/rcssNum",
4         "./css/var/cssExpand",
5         "./css/var/isHidden",
6         "./css/adjustCSS",
7         "./css/defaultDisplay",
9         "./core/init",
10         "./effects/Tween",
11         "./queue",
12         "./css",
13         "./deferred",
14         "./traversing"
15 ], function( jQuery, rcssNum, cssExpand, isHidden, adjustCSS, defaultDisplay ) {
17 var
18         fxNow, timerId,
19         rfxtypes = /^(?:toggle|show|hide)$/,
20         rrun = /queueHooks$/,
21         animationPrefilters = [ defaultPrefilter ],
22         tweeners = {
23                 "*": [ function( prop, value ) {
24                         var tween = this.createTween( prop, value );
25                         adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
26                         return tween;
27                 } ]
28         };
30 function raf() {
31         if ( timerId ) {
32                 window.requestAnimationFrame( raf );
33                 jQuery.fx.tick();
34         }
37 // Animations created synchronously will run synchronously
38 function createFxNow() {
39         setTimeout(function() {
40                 fxNow = undefined;
41         });
42         return ( fxNow = jQuery.now() );
45 // Generate parameters to create a standard animation
46 function genFx( type, includeWidth ) {
47         var which,
48                 attrs = { height: type },
49                 i = 0;
51         // if we include width, step value is 1 to do all cssExpand values,
52         // if we don't include width, step value is 2 to skip over Left and Right
53         includeWidth = includeWidth ? 1 : 0;
54         for ( ; i < 4 ; i += 2 - includeWidth ) {
55                 which = cssExpand[ i ];
56                 attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
57         }
59         if ( includeWidth ) {
60                 attrs.opacity = attrs.width = type;
61         }
63         return attrs;
66 function createTween( value, prop, animation ) {
67         var tween,
68                 collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
69                 index = 0,
70                 length = collection.length;
71         for ( ; index < length; index++ ) {
72                 if ( (tween = collection[ index ].call( animation, prop, value )) ) {
74                         // we're done with this property
75                         return tween;
76                 }
77         }
80 function defaultPrefilter( elem, props, opts ) {
81         /* jshint validthis: true */
82         var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
83                 anim = this,
84                 orig = {},
85                 style = elem.style,
86                 hidden = elem.nodeType && isHidden( elem ),
87                 dataShow = jQuery._data( elem, "fxshow" );
89         // handle queue: false promises
90         if ( !opts.queue ) {
91                 hooks = jQuery._queueHooks( elem, "fx" );
92                 if ( hooks.unqueued == null ) {
93                         hooks.unqueued = 0;
94                         oldfire = hooks.empty.fire;
95                         hooks.empty.fire = function() {
96                                 if ( !hooks.unqueued ) {
97                                         oldfire();
98                                 }
99                         };
100                 }
101                 hooks.unqueued++;
103                 anim.always(function() {
104                         // doing this makes sure that the complete handler will be called
105                         // before this completes
106                         anim.always(function() {
107                                 hooks.unqueued--;
108                                 if ( !jQuery.queue( elem, "fx" ).length ) {
109                                         hooks.empty.fire();
110                                 }
111                         });
112                 });
113         }
115         // height/width overflow pass
116         if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
117                 // Make sure that nothing sneaks out
118                 // Record all 3 overflow attributes because IE does not
119                 // change the overflow attribute when overflowX and
120                 // overflowY are set to the same value
121                 opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
123                 // Set display property to inline-block for height/width
124                 // animations on inline elements that are having width/height animated
125                 display = jQuery.css( elem, "display" );
127                 // Test default display if display is currently "none"
128                 checkDisplay = display === "none" ?
129                         jQuery._data( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;
131                 if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
132                         style.display = "inline-block";
133                 }
134         }
136         if ( opts.overflow ) {
137                 style.overflow = "hidden";
138                 anim.always(function() {
139                         style.overflow = opts.overflow[ 0 ];
140                         style.overflowX = opts.overflow[ 1 ];
141                         style.overflowY = opts.overflow[ 2 ];
142                 });
143         }
145         // show/hide pass
146         for ( prop in props ) {
147                 value = props[ prop ];
148                 if ( rfxtypes.exec( value ) ) {
149                         delete props[ prop ];
150                         toggle = toggle || value === "toggle";
151                         if ( value === ( hidden ? "hide" : "show" ) ) {
153                                 // If there is dataShow left over from a stopped hide or show
154                                 // and we are going to proceed with show, we should pretend to be hidden
155                                 if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
156                                         hidden = true;
157                                 } else {
158                                         continue;
159                                 }
160                         }
161                         orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
163                 // Any non-fx value stops us from restoring the original display value
164                 } else {
165                         display = undefined;
166                 }
167         }
169         if ( !jQuery.isEmptyObject( orig ) ) {
170                 if ( dataShow ) {
171                         if ( "hidden" in dataShow ) {
172                                 hidden = dataShow.hidden;
173                         }
174                 } else {
175                         dataShow = jQuery._data( elem, "fxshow", {} );
176                 }
178                 // store state if its toggle - enables .stop().toggle() to "reverse"
179                 if ( toggle ) {
180                         dataShow.hidden = !hidden;
181                 }
182                 if ( hidden ) {
183                         jQuery( elem ).show();
184                 } else {
185                         anim.done(function() {
186                                 jQuery( elem ).hide();
187                         });
188                 }
189                 anim.done(function() {
190                         var prop;
191                         jQuery._removeData( elem, "fxshow" );
192                         for ( prop in orig ) {
193                                 jQuery.style( elem, prop, orig[ prop ] );
194                         }
195                 });
196                 for ( prop in orig ) {
197                         tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
199                         if ( !( prop in dataShow ) ) {
200                                 dataShow[ prop ] = tween.start;
201                                 if ( hidden ) {
202                                         tween.end = tween.start;
203                                         tween.start = prop === "width" || prop === "height" ? 1 : 0;
204                                 }
205                         }
206                 }
208         // If this is a noop like .hide().hide(), restore an overwritten display value
209         } else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) {
210                 style.display = display;
211         }
214 function propFilter( props, specialEasing ) {
215         var index, name, easing, value, hooks;
217         // camelCase, specialEasing and expand cssHook pass
218         for ( index in props ) {
219                 name = jQuery.camelCase( index );
220                 easing = specialEasing[ name ];
221                 value = props[ index ];
222                 if ( jQuery.isArray( value ) ) {
223                         easing = value[ 1 ];
224                         value = props[ index ] = value[ 0 ];
225                 }
227                 if ( index !== name ) {
228                         props[ name ] = value;
229                         delete props[ index ];
230                 }
232                 hooks = jQuery.cssHooks[ name ];
233                 if ( hooks && "expand" in hooks ) {
234                         value = hooks.expand( value );
235                         delete props[ name ];
237                         // not quite $.extend, this wont overwrite keys already present.
238                         // also - reusing 'index' from above because we have the correct "name"
239                         for ( index in value ) {
240                                 if ( !( index in props ) ) {
241                                         props[ index ] = value[ index ];
242                                         specialEasing[ index ] = easing;
243                                 }
244                         }
245                 } else {
246                         specialEasing[ name ] = easing;
247                 }
248         }
251 function Animation( elem, properties, options ) {
252         var result,
253                 stopped,
254                 index = 0,
255                 length = animationPrefilters.length,
256                 deferred = jQuery.Deferred().always( function() {
257                         // don't match elem in the :animated selector
258                         delete tick.elem;
259                 }),
260                 tick = function() {
261                         if ( stopped ) {
262                                 return false;
263                         }
264                         var currentTime = fxNow || createFxNow(),
265                                 remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
266                                 // Support: Android 2.3
267                                 // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
268                                 temp = remaining / animation.duration || 0,
269                                 percent = 1 - temp,
270                                 index = 0,
271                                 length = animation.tweens.length;
273                         for ( ; index < length ; index++ ) {
274                                 animation.tweens[ index ].run( percent );
275                         }
277                         deferred.notifyWith( elem, [ animation, percent, remaining ]);
279                         if ( percent < 1 && length ) {
280                                 return remaining;
281                         } else {
282                                 deferred.resolveWith( elem, [ animation ] );
283                                 return false;
284                         }
285                 },
286                 animation = deferred.promise({
287                         elem: elem,
288                         props: jQuery.extend( {}, properties ),
289                         opts: jQuery.extend( true, {
290                                 specialEasing: {},
291                                 easing: jQuery.easing._default
292                         }, 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
318                                 // otherwise, reject
319                                 if ( gotoEnd ) {
320                                         deferred.resolveWith( elem, [ animation, gotoEnd ] );
321                                 } else {
322                                         deferred.rejectWith( elem, [ animation, gotoEnd ] );
323                                 }
324                                 return this;
325                         }
326                 }),
327                 props = animation.props;
329         propFilter( props, animation.opts.specialEasing );
331         for ( ; index < length ; index++ ) {
332                 result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
333                 if ( result ) {
334                         return result;
335                 }
336         }
338         jQuery.map( props, createTween, animation );
340         if ( jQuery.isFunction( animation.opts.start ) ) {
341                 animation.opts.start.call( elem, animation );
342         }
344         jQuery.fx.timer(
345                 jQuery.extend( tick, {
346                         elem: elem,
347                         anim: animation,
348                         queue: animation.opts.queue
349                 })
350         );
352         // attach callbacks from options
353         return animation.progress( animation.opts.progress )
354                 .done( animation.opts.done, animation.opts.complete )
355                 .fail( animation.opts.fail )
356                 .always( animation.opts.always );
359 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 ) {
429                 // show any hidden elements after setting opacity to 0
430                 return this.filter( isHidden ).css( "opacity", 0 ).show()
432                         // animate to the value specified
433                         .end().animate({ opacity: to }, speed, easing, callback );
434         },
435         animate: function( prop, speed, easing, callback ) {
436                 var empty = jQuery.isEmptyObject( prop ),
437                         optall = jQuery.speed( speed, easing, callback ),
438                         doAnimation = function() {
439                                 // Operate on a copy of prop so per-property easing won't be lost
440                                 var anim = Animation( this, jQuery.extend( {}, prop ), optall );
442                                 // Empty animations, or finishing resolves immediately
443                                 if ( empty || jQuery._data( this, "finish" ) ) {
444                                         anim.stop( true );
445                                 }
446                         };
447                         doAnimation.finish = doAnimation;
449                 return empty || optall.queue === false ?
450                         this.each( doAnimation ) :
451                         this.queue( optall.queue, doAnimation );
452         },
453         stop: function( type, clearQueue, gotoEnd ) {
454                 var stopQueue = function( hooks ) {
455                         var stop = hooks.stop;
456                         delete hooks.stop;
457                         stop( gotoEnd );
458                 };
460                 if ( typeof type !== "string" ) {
461                         gotoEnd = clearQueue;
462                         clearQueue = type;
463                         type = undefined;
464                 }
465                 if ( clearQueue && type !== false ) {
466                         this.queue( type || "fx", [] );
467                 }
469                 return this.each(function() {
470                         var dequeue = true,
471                                 index = type != null && type + "queueHooks",
472                                 timers = jQuery.timers,
473                                 data = jQuery._data( this );
475                         if ( index ) {
476                                 if ( data[ index ] && data[ index ].stop ) {
477                                         stopQueue( data[ index ] );
478                                 }
479                         } else {
480                                 for ( index in data ) {
481                                         if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
482                                                 stopQueue( data[ index ] );
483                                         }
484                                 }
485                         }
487                         for ( index = timers.length; index--; ) {
488                                 if ( timers[ index ].elem === this &&
489                                         (type == null || timers[ index ].queue === type) ) {
491                                         timers[ index ].anim.stop( gotoEnd );
492                                         dequeue = false;
493                                         timers.splice( index, 1 );
494                                 }
495                         }
497                         // start the next in the queue if the last step wasn't forced
498                         // timers currently will call their complete callbacks, which will dequeue
499                         // but only if they were gotoEnd
500                         if ( dequeue || !gotoEnd ) {
501                                 jQuery.dequeue( this, type );
502                         }
503                 });
504         },
505         finish: function( type ) {
506                 if ( type !== false ) {
507                         type = type || "fx";
508                 }
509                 return this.each(function() {
510                         var index,
511                                 data = jQuery._data( this ),
512                                 queue = data[ type + "queue" ],
513                                 hooks = data[ type + "queueHooks" ],
514                                 timers = jQuery.timers,
515                                 length = queue ? queue.length : 0;
517                         // enable finishing flag on private data
518                         data.finish = true;
520                         // empty the queue first
521                         jQuery.queue( this, type, [] );
523                         if ( hooks && hooks.stop ) {
524                                 hooks.stop.call( this, true );
525                         }
527                         // look for any active animations, and finish them
528                         for ( index = timers.length; index--; ) {
529                                 if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
530                                         timers[ index ].anim.stop( true );
531                                         timers.splice( index, 1 );
532                                 }
533                         }
535                         // look for any animations in the old queue and finish them
536                         for ( index = 0; index < length; index++ ) {
537                                 if ( queue[ index ] && queue[ index ].finish ) {
538                                         queue[ index ].finish.call( this );
539                                 }
540                         }
542                         // turn off finishing flag
543                         delete data.finish;
544                 });
545         }
548 jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
549         var cssFn = jQuery.fn[ name ];
550         jQuery.fn[ name ] = function( speed, easing, callback ) {
551                 return speed == null || typeof speed === "boolean" ?
552                         cssFn.apply( this, arguments ) :
553                         this.animate( genFx( name, true ), speed, easing, callback );
554         };
557 // Generate shortcuts for custom animations
558 jQuery.each({
559         slideDown: genFx("show"),
560         slideUp: genFx("hide"),
561         slideToggle: genFx("toggle"),
562         fadeIn: { opacity: "show" },
563         fadeOut: { opacity: "hide" },
564         fadeToggle: { opacity: "toggle" }
565 }, function( name, props ) {
566         jQuery.fn[ name ] = function( speed, easing, callback ) {
567                 return this.animate( props, speed, easing, callback );
568         };
571 jQuery.timers = [];
572 jQuery.fx.tick = function() {
573         var timer,
574                 timers = jQuery.timers,
575                 i = 0;
577         fxNow = jQuery.now();
579         for ( ; i < timers.length; i++ ) {
580                 timer = timers[ i ];
581                 // Checks the timer has not already been removed
582                 if ( !timer() && timers[ i ] === timer ) {
583                         timers.splice( i--, 1 );
584                 }
585         }
587         if ( !timers.length ) {
588                 jQuery.fx.stop();
589         }
590         fxNow = undefined;
593 jQuery.fx.timer = function( timer ) {
594         jQuery.timers.push( timer );
595         if ( timer() ) {
596                 jQuery.fx.start();
597         } else {
598                 jQuery.timers.pop();
599         }
602 jQuery.fx.interval = 13;
604 jQuery.fx.start = function() {
605         timerId = window.requestAnimationFrame ?
606                 window.requestAnimationFrame( raf ) :
607                 setInterval( jQuery.fx.tick, jQuery.fx.interval );
610 jQuery.fx.stop = function() {
611         if ( window.cancelAnimationFrame ) {
612                 window.cancelAnimationFrame( timerId );
613         } else {
614                 clearInterval( timerId );
615         }
617         timerId = null;
620 jQuery.fx.speeds = {
621         slow: 600,
622         fast: 200,
623         // Default speed
624         _default: 400
627 return jQuery;