diff --git a/src/css.js b/src/css.js index 286eef64b..7b011668e 100644 --- a/src/css.js +++ b/src/css.js @@ -11,16 +11,15 @@ define([ "./css/var/swap", "./css/curCSS", "./css/adjustCSS", - "./css/defaultDisplay", "./css/addGetHookIf", "./css/support", - "./data/var/dataPriv", + "./css/showHide", "./core/init", "./core/ready", "./selector" // contains ], function( jQuery, pnum, access, rmargin, rcssNum, rnumnonpx, cssExpand, isHidden, - getStyles, swap, curCSS, adjustCSS, defaultDisplay, addGetHookIf, support, dataPriv ) { + getStyles, swap, curCSS, adjustCSS, addGetHookIf, support, showHide ) { var // Swappable if display is none or starts with table @@ -151,65 +150,6 @@ function getWidthOrHeight( elem, name, extra ) { ) + "px"; } -function showHide( elements, show ) { - var display, elem, hidden, - values = [], - index = 0, - length = elements.length; - - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - values[ index ] = dataPriv.get( elem, "olddisplay" ); - display = elem.style.display; - if ( show ) { - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !values[ index ] && display === "none" ) { - elem.style.display = ""; - } - - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( elem.style.display === "" && isHidden( elem ) ) { - values[ index ] = dataPriv.access( - elem, - "olddisplay", - defaultDisplay(elem.nodeName) - ); - } - } else { - hidden = isHidden( elem ); - - if ( display !== "none" || !hidden ) { - dataPriv.set( - elem, - "olddisplay", - hidden ? display : jQuery.css( elem, "display" ) - ); - } - } - } - - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( index = 0; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - if ( !show || elem.style.display === "none" || elem.style.display === "" ) { - elem.style.display = show ? values[ index ] || "" : "none"; - } - } - - return elements; -} - jQuery.extend({ // Add in style property hooks for overriding the default diff --git a/src/css/defaultDisplay.js b/src/css/defaultDisplay.js deleted file mode 100644 index 3771be6d1..000000000 --- a/src/css/defaultDisplay.js +++ /dev/null @@ -1,71 +0,0 @@ -define([ - "../core", - "../var/document", - "../manipulation" // appendTo -], function( jQuery, document ) { - -var iframe, - elemdisplay = { - - // Support: Firefox - // We have to pre-define these values for FF (#10227) - HTML: "block", - BODY: "block" - }; - -/** - * Retrieve the actual display of a element - * @param {String} name nodeName of the element - * @param {Object} doc Document object - */ -// Called only from within defaultDisplay -function actualDisplay( name, doc ) { - var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), - - display = jQuery.css( elem[ 0 ], "display" ); - - // We don't have any data stored on the element, - // so use "detach" method as fast way to get rid of the element - elem.detach(); - - return display; -} - -/** - * Try to determine the default display value of an element - * @param {String} nodeName - */ -function defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - - // Use the already-created iframe if possible - iframe = (iframe || jQuery( "" )) - .appendTo( doc.documentElement ); - - // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse - doc = iframe[ 0 ].contentDocument; - - // Support: IE - doc.write(); - doc.close(); - - display = actualDisplay( nodeName, doc ); - iframe.detach(); - } - - // Store the correct default display - elemdisplay[ nodeName ] = display; - } - - return display; -} - -return defaultDisplay; -}); diff --git a/src/css/showHide.js b/src/css/showHide.js new file mode 100644 index 000000000..d0cca589b --- /dev/null +++ b/src/css/showHide.js @@ -0,0 +1,47 @@ +define([ + "../data/var/dataPriv" +], function( dataPriv ) { + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + if ( display === "none" ) { + // Restore a pre-hide() value if we have one + values[ index ] = dataPriv.get( elem, "display" ) || ""; + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember the value we're replacing + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +return showHide; + +}); diff --git a/src/effects.js b/src/effects.js index 4ead51ccd..3268fcda9 100644 --- a/src/effects.js +++ b/src/effects.js @@ -4,18 +4,19 @@ define([ "./var/rcssNum", "./css/var/cssExpand", "./css/var/isHidden", + "./css/var/swap", "./css/adjustCSS", - "./css/defaultDisplay", "./data/var/dataPriv", + "./css/showHide", "./core/init", - "./effects/Tween", "./queue", - "./css", "./deferred", - "./traversing" -], function( jQuery, document, rcssNum, cssExpand, - isHidden, adjustCSS, defaultDisplay, dataPriv ) { + "./traversing", + "./manipulation", + "./css", + "./effects/Tween" +], function( jQuery, document, rcssNum, cssExpand, isHidden, swap, adjustCSS, dataPriv, showHide ) { var fxNow, timerId, @@ -82,14 +83,15 @@ function createTween( value, prop, animation ) { function defaultPrefilter( elem, props, opts ) { /* jshint validthis: true */ - var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay, + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, anim = this, orig = {}, style = elem.style, hidden = elem.nodeType && isHidden( elem ), dataShow = dataPriv.get( elem, "fxshow" ); - // Handle queue: false promises + // Queue-skipping animations hijack the fx hooks if ( !opts.queue ) { hooks = jQuery._queueHooks( elem, "fx" ); if ( hooks.unqueued == null ) { @@ -114,24 +116,69 @@ function defaultPrefilter( elem, props, opts ) { }); } - // Height/width overflow pass - if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { - // Make sure that nothing sneaks out - // Record all 3 overflow attributes because IE9-10 do not - // change the overflow attribute when overflowX and - // overflowY are set to the same value + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + // Support: IE 9 - 11 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - // Set display property to inline-block for height/width - // animations on inline elements that are having width/height animated + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + display = restoreDisplay || swap( elem, { "display": "" }, function() { + return jQuery.css( elem, "display" ); + } ); + } - // Test default display if display is currently "none" - checkDisplay = display === "none" ? - dataPriv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display; + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { - if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) { - style.display = "inline-block"; + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done(function() { + style.display = restoreDisplay; + }); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } } } @@ -144,73 +191,52 @@ function defaultPrefilter( elem, props, opts ) { }); } - // show/hide pass - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.exec( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { - // If there is dataShow left over from a stopped hide or show - // and we are going to proceed with show, we should pretend to be hidden - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - } else { - continue; + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - // Any non-fx value stops us from restoring the original display value - } else { - display = undefined; - } - } - - if ( !jQuery.isEmptyObject( orig ) ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; } - } else { - dataShow = dataPriv.access( elem, "fxshow", {} ); - } - // Store state if its toggle - enables .stop().toggle() to "reverse" - if ( toggle ) { - dataShow.hidden = !hidden; - } - if ( hidden ) { - jQuery( elem ).show(); - } else { + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* jshint -W083 */ anim.done(function() { - jQuery( elem ).hide(); + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } }); } - anim.done(function() { - var prop; - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - }); - for ( prop in orig ) { - tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = tween.start; - if ( hidden ) { - tween.end = tween.start; - tween.start = prop === "width" || prop === "height" ? 1 : 0; - } + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = prop === "width" || prop === "height" ? 1 : 0; } } - - // If this is a noop like .hide().hide(), restore an overwritten display value - } else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) { - style.display = display; } } diff --git a/test/data/testsuite.css b/test/data/testsuite.css index cf2ba8c20..50619b98d 100644 --- a/test/data/testsuite.css +++ b/test/data/testsuite.css @@ -68,7 +68,8 @@ div.noopacity { opacity: 0; } -div.hidden { +div.hidden, +span.hidden { display: none; } @@ -116,19 +117,10 @@ div#fx-tests div.noback { display: none; } -/* tests to ensure jQuery can determine the native display mode of elements - that have been set as display: none in stylesheets */ -div#show-tests * { display: none; } - #nothiddendiv { font-size: 16px; } #nothiddendivchild.em { font-size: 2em; } #nothiddendivchild.prct { font-size: 150%; } -/* 8099 changes to default styles are read correctly */ -tt { display: none; } -sup { display: none; } -dfn { display: none; } - /* #9239 Attach a background to the body( avoid crashes in removing the test element in support ) */ body, div { background: url(http://static.jquery.com/files/rocker/images/logo_jquery_215x53.gif) no-repeat -1000px 0; } diff --git a/test/unit/css.js b/test/unit/css.js index 3117b12d7..b977b067c 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -475,14 +475,14 @@ test("show(); hide()", function() { hiddendiv = jQuery("div.hidden"); hiddendiv.hide(); - equal( hiddendiv.css("display"), "none", "Non-detached div hidden" ); + equal( hiddendiv.css("display"), "none", "Cascade-hidden div after hide()" ); hiddendiv.show(); - equal( hiddendiv.css("display"), "block", "Pre-hidden div shown" ); + equal( hiddendiv.css("display"), "none", "Show does not trump CSS cascade" ); div = jQuery("