Build: migrate grunt authors to a custom script
[jquery.git] / src / effects.js
blob3c1bd33fd0b8fb41925432dfec179c27337b37c5
1 import { jQuery } from "./core.js";
2 import { document } from "./var/document.js";
3 import { rcssNum } from "./var/rcssNum.js";
4 import { rnothtmlwhite } from "./var/rnothtmlwhite.js";
5 import { cssExpand } from "./css/var/cssExpand.js";
6 import { isHiddenWithinTree } from "./css/var/isHiddenWithinTree.js";
7 import { adjustCSS } from "./css/adjustCSS.js";
8 import { cssCamelCase } from "./css/cssCamelCase.js";
9 import { dataPriv } from "./data/var/dataPriv.js";
10 import { showHide } from "./css/showHide.js";
12 import "./core/init.js";
13 import "./queue.js";
14 import "./deferred.js";
15 import "./traversing.js";
16 import "./manipulation.js";
17 import "./css.js";
18 import "./effects/Tween.js";
20 var
21 fxNow, inProgress,
22 rfxtypes = /^(?:toggle|show|hide)$/,
23 rrun = /queueHooks$/;
25 function schedule() {
26 if ( inProgress ) {
27 if ( document.hidden === false && window.requestAnimationFrame ) {
28 window.requestAnimationFrame( schedule );
29 } else {
30 window.setTimeout( schedule, 13 );
33 jQuery.fx.tick();
37 // Animations created synchronously will run synchronously
38 function createFxNow() {
39 window.setTimeout( function() {
40 fxNow = undefined;
41 } );
42 return ( fxNow = Date.now() );
45 // Generate parameters to create a standard animation
46 function genFx( type, includeWidth ) {
47 var which,
48 i = 0,
49 attrs = { height: type };
51 // If we include width, step value is 1 to do all cssExpand values,
52 // otherwise 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;
59 if ( includeWidth ) {
60 attrs.opacity = attrs.width = type;
63 return attrs;
66 function createTween( value, prop, animation ) {
67 var tween,
68 collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.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;
80 function defaultPrefilter( elem, props, opts ) {
81 var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
82 isBox = "width" in props || "height" in props,
83 anim = this,
84 orig = {},
85 style = elem.style,
86 hidden = elem.nodeType && isHiddenWithinTree( elem ),
87 dataShow = dataPriv.get( elem, "fxshow" );
89 // Queue-skipping animations hijack the fx hooks
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();
101 hooks.unqueued++;
103 anim.always( function() {
105 // Ensure the complete handler is called before this completes
106 anim.always( function() {
107 hooks.unqueued--;
108 if ( !jQuery.queue( elem, "fx" ).length ) {
109 hooks.empty.fire();
111 } );
112 } );
115 // Detect show/hide animations
116 for ( prop in props ) {
117 value = props[ prop ];
118 if ( rfxtypes.test( value ) ) {
119 delete props[ prop ];
120 toggle = toggle || value === "toggle";
121 if ( value === ( hidden ? "hide" : "show" ) ) {
123 // Pretend to be hidden if this is a "show" and
124 // there is still data from a stopped show/hide
125 if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
126 hidden = true;
128 // Ignore all other no-op show/hide data
129 } else {
130 continue;
133 orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
137 // Bail out if this is a no-op like .hide().hide()
138 propTween = !jQuery.isEmptyObject( props );
139 if ( !propTween && jQuery.isEmptyObject( orig ) ) {
140 return;
143 // Restrict "overflow" and "display" styles during box animations
144 if ( isBox && elem.nodeType === 1 ) {
146 // Support: IE <=9 - 11+
147 // Record all 3 overflow attributes because IE does not infer the shorthand
148 // from identically-valued overflowX and overflowY.
149 opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
151 // Identify a display type, preferring old show/hide data over the CSS cascade
152 restoreDisplay = dataShow && dataShow.display;
153 if ( restoreDisplay == null ) {
154 restoreDisplay = dataPriv.get( elem, "display" );
156 display = jQuery.css( elem, "display" );
157 if ( display === "none" ) {
158 if ( restoreDisplay ) {
159 display = restoreDisplay;
160 } else {
162 // Get nonempty value(s) by temporarily forcing visibility
163 showHide( [ elem ], true );
164 restoreDisplay = elem.style.display || restoreDisplay;
165 display = jQuery.css( elem, "display" );
166 showHide( [ elem ] );
170 // Animate inline elements as inline-block
171 if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
172 if ( jQuery.css( elem, "float" ) === "none" ) {
174 // Restore the original display value at the end of pure show/hide animations
175 if ( !propTween ) {
176 anim.done( function() {
177 style.display = restoreDisplay;
178 } );
179 if ( restoreDisplay == null ) {
180 display = style.display;
181 restoreDisplay = display === "none" ? "" : display;
184 style.display = "inline-block";
189 if ( opts.overflow ) {
190 style.overflow = "hidden";
191 anim.always( function() {
192 style.overflow = opts.overflow[ 0 ];
193 style.overflowX = opts.overflow[ 1 ];
194 style.overflowY = opts.overflow[ 2 ];
195 } );
198 // Implement show/hide animations
199 propTween = false;
200 for ( prop in orig ) {
202 // General show/hide setup for this element animation
203 if ( !propTween ) {
204 if ( dataShow ) {
205 if ( "hidden" in dataShow ) {
206 hidden = dataShow.hidden;
208 } else {
209 dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
212 // Store hidden/visible for toggle so `.stop().toggle()` "reverses"
213 if ( toggle ) {
214 dataShow.hidden = !hidden;
217 // Show elements before animating them
218 if ( hidden ) {
219 showHide( [ elem ], true );
222 // eslint-disable-next-line no-loop-func
223 anim.done( function() {
225 // The final step of a "hide" animation is actually hiding the element
226 if ( !hidden ) {
227 showHide( [ elem ] );
229 dataPriv.remove( elem, "fxshow" );
230 for ( prop in orig ) {
231 jQuery.style( elem, prop, orig[ prop ] );
233 } );
236 // Per-property setup
237 propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
238 if ( !( prop in dataShow ) ) {
239 dataShow[ prop ] = propTween.start;
240 if ( hidden ) {
241 propTween.end = propTween.start;
242 propTween.start = 0;
248 function propFilter( props, specialEasing ) {
249 var index, name, easing, value, hooks;
251 // camelCase, specialEasing and expand cssHook pass
252 for ( index in props ) {
253 name = cssCamelCase( index );
254 easing = specialEasing[ name ];
255 value = props[ index ];
256 if ( Array.isArray( value ) ) {
257 easing = value[ 1 ];
258 value = props[ index ] = value[ 0 ];
261 if ( index !== name ) {
262 props[ name ] = value;
263 delete props[ index ];
266 hooks = jQuery.cssHooks[ name ];
267 if ( hooks && "expand" in hooks ) {
268 value = hooks.expand( value );
269 delete props[ name ];
271 // Not quite $.extend, this won't overwrite existing keys.
272 // Reusing 'index' because we have the correct "name"
273 for ( index in value ) {
274 if ( !( index in props ) ) {
275 props[ index ] = value[ index ];
276 specialEasing[ index ] = easing;
279 } else {
280 specialEasing[ name ] = easing;
285 function Animation( elem, properties, options ) {
286 var result,
287 stopped,
288 index = 0,
289 length = Animation.prefilters.length,
290 deferred = jQuery.Deferred().always( function() {
292 // Don't match elem in the :animated selector
293 delete tick.elem;
294 } ),
295 tick = function() {
296 if ( stopped ) {
297 return false;
299 var currentTime = fxNow || createFxNow(),
300 remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
302 percent = 1 - ( remaining / animation.duration || 0 ),
303 index = 0,
304 length = animation.tweens.length;
306 for ( ; index < length; index++ ) {
307 animation.tweens[ index ].run( percent );
310 deferred.notifyWith( elem, [ animation, percent, remaining ] );
312 // If there's more to do, yield
313 if ( percent < 1 && length ) {
314 return remaining;
317 // If this was an empty animation, synthesize a final progress notification
318 if ( !length ) {
319 deferred.notifyWith( elem, [ animation, 1, 0 ] );
322 // Resolve the animation and report its conclusion
323 deferred.resolveWith( elem, [ animation ] );
324 return false;
326 animation = deferred.promise( {
327 elem: elem,
328 props: jQuery.extend( {}, properties ),
329 opts: jQuery.extend( true, {
330 specialEasing: {},
331 easing: jQuery.easing._default
332 }, options ),
333 originalProperties: properties,
334 originalOptions: options,
335 startTime: fxNow || createFxNow(),
336 duration: options.duration,
337 tweens: [],
338 createTween: function( prop, end ) {
339 var tween = jQuery.Tween( elem, animation.opts, prop, end,
340 animation.opts.specialEasing[ prop ] || animation.opts.easing );
341 animation.tweens.push( tween );
342 return tween;
344 stop: function( gotoEnd ) {
345 var index = 0,
347 // If we are going to the end, we want to run all the tweens
348 // otherwise we skip this part
349 length = gotoEnd ? animation.tweens.length : 0;
350 if ( stopped ) {
351 return this;
353 stopped = true;
354 for ( ; index < length; index++ ) {
355 animation.tweens[ index ].run( 1 );
358 // Resolve when we played the last frame; otherwise, reject
359 if ( gotoEnd ) {
360 deferred.notifyWith( elem, [ animation, 1, 0 ] );
361 deferred.resolveWith( elem, [ animation, gotoEnd ] );
362 } else {
363 deferred.rejectWith( elem, [ animation, gotoEnd ] );
365 return this;
367 } ),
368 props = animation.props;
370 propFilter( props, animation.opts.specialEasing );
372 for ( ; index < length; index++ ) {
373 result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
374 if ( result ) {
375 if ( typeof result.stop === "function" ) {
376 jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
377 result.stop.bind( result );
379 return result;
383 jQuery.map( props, createTween, animation );
385 if ( typeof animation.opts.start === "function" ) {
386 animation.opts.start.call( elem, animation );
389 // Attach callbacks from options
390 animation
391 .progress( animation.opts.progress )
392 .done( animation.opts.done, animation.opts.complete )
393 .fail( animation.opts.fail )
394 .always( animation.opts.always );
396 jQuery.fx.timer(
397 jQuery.extend( tick, {
398 elem: elem,
399 anim: animation,
400 queue: animation.opts.queue
404 return animation;
407 jQuery.Animation = jQuery.extend( Animation, {
409 tweeners: {
410 "*": [ function( prop, value ) {
411 var tween = this.createTween( prop, value );
412 adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
413 return tween;
417 tweener: function( props, callback ) {
418 if ( typeof props === "function" ) {
419 callback = props;
420 props = [ "*" ];
421 } else {
422 props = props.match( rnothtmlwhite );
425 var prop,
426 index = 0,
427 length = props.length;
429 for ( ; index < length; index++ ) {
430 prop = props[ index ];
431 Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
432 Animation.tweeners[ prop ].unshift( callback );
436 prefilters: [ defaultPrefilter ],
438 prefilter: function( callback, prepend ) {
439 if ( prepend ) {
440 Animation.prefilters.unshift( callback );
441 } else {
442 Animation.prefilters.push( callback );
445 } );
447 jQuery.speed = function( speed, easing, fn ) {
448 var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
449 complete: fn || easing ||
450 typeof speed === "function" && speed,
451 duration: speed,
452 easing: fn && easing || easing && typeof easing !== "function" && easing
455 // Go to the end state if fx are off
456 if ( jQuery.fx.off ) {
457 opt.duration = 0;
459 } else {
460 if ( typeof opt.duration !== "number" ) {
461 if ( opt.duration in jQuery.fx.speeds ) {
462 opt.duration = jQuery.fx.speeds[ opt.duration ];
464 } else {
465 opt.duration = jQuery.fx.speeds._default;
470 // Normalize opt.queue - true/undefined/null -> "fx"
471 if ( opt.queue == null || opt.queue === true ) {
472 opt.queue = "fx";
475 // Queueing
476 opt.old = opt.complete;
478 opt.complete = function() {
479 if ( typeof opt.old === "function" ) {
480 opt.old.call( this );
483 if ( opt.queue ) {
484 jQuery.dequeue( this, opt.queue );
488 return opt;
491 jQuery.fn.extend( {
492 fadeTo: function( speed, to, easing, callback ) {
494 // Show any hidden elements after setting opacity to 0
495 return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
497 // Animate to the value specified
498 .end().animate( { opacity: to }, speed, easing, callback );
500 animate: function( prop, speed, easing, callback ) {
501 var empty = jQuery.isEmptyObject( prop ),
502 optall = jQuery.speed( speed, easing, callback ),
503 doAnimation = function() {
505 // Operate on a copy of prop so per-property easing won't be lost
506 var anim = Animation( this, jQuery.extend( {}, prop ), optall );
508 // Empty animations, or finishing resolves immediately
509 if ( empty || dataPriv.get( this, "finish" ) ) {
510 anim.stop( true );
514 doAnimation.finish = doAnimation;
516 return empty || optall.queue === false ?
517 this.each( doAnimation ) :
518 this.queue( optall.queue, doAnimation );
520 stop: function( type, clearQueue, gotoEnd ) {
521 var stopQueue = function( hooks ) {
522 var stop = hooks.stop;
523 delete hooks.stop;
524 stop( gotoEnd );
527 if ( typeof type !== "string" ) {
528 gotoEnd = clearQueue;
529 clearQueue = type;
530 type = undefined;
532 if ( clearQueue ) {
533 this.queue( type || "fx", [] );
536 return this.each( function() {
537 var dequeue = true,
538 index = type != null && type + "queueHooks",
539 timers = jQuery.timers,
540 data = dataPriv.get( this );
542 if ( index ) {
543 if ( data[ index ] && data[ index ].stop ) {
544 stopQueue( data[ index ] );
546 } else {
547 for ( index in data ) {
548 if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
549 stopQueue( data[ index ] );
554 for ( index = timers.length; index--; ) {
555 if ( timers[ index ].elem === this &&
556 ( type == null || timers[ index ].queue === type ) ) {
558 timers[ index ].anim.stop( gotoEnd );
559 dequeue = false;
560 timers.splice( index, 1 );
564 // Start the next in the queue if the last step wasn't forced.
565 // Timers currently will call their complete callbacks, which
566 // will dequeue but only if they were gotoEnd.
567 if ( dequeue || !gotoEnd ) {
568 jQuery.dequeue( this, type );
570 } );
572 finish: function( type ) {
573 if ( type !== false ) {
574 type = type || "fx";
576 return this.each( function() {
577 var index,
578 data = dataPriv.get( this ),
579 queue = data[ type + "queue" ],
580 hooks = data[ type + "queueHooks" ],
581 timers = jQuery.timers,
582 length = queue ? queue.length : 0;
584 // Enable finishing flag on private data
585 data.finish = true;
587 // Empty the queue first
588 jQuery.queue( this, type, [] );
590 if ( hooks && hooks.stop ) {
591 hooks.stop.call( this, true );
594 // Look for any active animations, and finish them
595 for ( index = timers.length; index--; ) {
596 if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
597 timers[ index ].anim.stop( true );
598 timers.splice( index, 1 );
602 // Look for any animations in the old queue and finish them
603 for ( index = 0; index < length; index++ ) {
604 if ( queue[ index ] && queue[ index ].finish ) {
605 queue[ index ].finish.call( this );
609 // Turn off finishing flag
610 delete data.finish;
611 } );
613 } );
615 jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) {
616 var cssFn = jQuery.fn[ name ];
617 jQuery.fn[ name ] = function( speed, easing, callback ) {
618 return speed == null || typeof speed === "boolean" ?
619 cssFn.apply( this, arguments ) :
620 this.animate( genFx( name, true ), speed, easing, callback );
622 } );
624 // Generate shortcuts for custom animations
625 jQuery.each( {
626 slideDown: genFx( "show" ),
627 slideUp: genFx( "hide" ),
628 slideToggle: genFx( "toggle" ),
629 fadeIn: { opacity: "show" },
630 fadeOut: { opacity: "hide" },
631 fadeToggle: { opacity: "toggle" }
632 }, function( name, props ) {
633 jQuery.fn[ name ] = function( speed, easing, callback ) {
634 return this.animate( props, speed, easing, callback );
636 } );
638 jQuery.timers = [];
639 jQuery.fx.tick = function() {
640 var timer,
641 i = 0,
642 timers = jQuery.timers;
644 fxNow = Date.now();
646 for ( ; i < timers.length; i++ ) {
647 timer = timers[ i ];
649 // Run the timer and safely remove it when done (allowing for external removal)
650 if ( !timer() && timers[ i ] === timer ) {
651 timers.splice( i--, 1 );
655 if ( !timers.length ) {
656 jQuery.fx.stop();
658 fxNow = undefined;
661 jQuery.fx.timer = function( timer ) {
662 jQuery.timers.push( timer );
663 jQuery.fx.start();
666 jQuery.fx.start = function() {
667 if ( inProgress ) {
668 return;
671 inProgress = true;
672 schedule();
675 jQuery.fx.stop = function() {
676 inProgress = null;
679 jQuery.fx.speeds = {
680 slow: 600,
681 fast: 200,
683 // Default speed
684 _default: 400
687 export { jQuery, jQuery as $ };