diff --git a/src/css.js b/src/css.js index 2616572a7..d8aa6d605 100644 --- a/src/css.js +++ b/src/css.js @@ -164,7 +164,7 @@ jQuery.extend({ } }, - // Exclude the following css properties to add px + // Don't automatically add "px" to these possibly-unitless properties cssNumber: { "columnCount": true, "fillOpacity": true, diff --git a/src/effects.js b/src/effects.js index 6f8685699..bb10b73f8 100644 --- a/src/effects.js +++ b/src/effects.js @@ -5,44 +5,51 @@ var fxNow, timerId, animationPrefilters = [ defaultPrefilter ], tweeners = { "*": [function( prop, value ) { - var end, unit, - tween = this.createTween( prop, value ), - parts = rfxnum.exec( value ), + var tween = this.createTween( prop, value ), target = tween.cur(), - start = +target || 0, + parts = rfxnum.exec( value ), + unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) && + rfxnum.exec( jQuery.css( tween.elem, prop ) ), scale = 1, maxIterations = 20; - if ( parts ) { - end = +parts[2]; - unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + if ( start && start[ 3 ] !== unit ) { + // Trust units reported by jQuery.css + unit = unit || start[ 3 ]; - // We need to compute starting value - if ( unit !== "px" && start ) { - // Iteratively approximate from a nonzero starting point - // Prefer the current property, because this process will be trivial if it uses the same units - // Fallback to end or a simple constant - start = jQuery.css( tween.elem, prop, true ) || end || 1; + // Make sure we update the tween properties later on + parts = parts || []; - do { - // If previous iteration zeroed out, double until we get *something* - // Use a string for doubling factor so we don't accidentally see scale as unchanged below - scale = scale || ".5"; + // Iteratively approximate from a nonzero starting point + start = +target || 1; - // Adjust and apply - start = start / scale; - jQuery.style( tween.elem, prop, start + unit ); + do { + // If previous iteration zeroed out, double until we get *something* + // Use a string for doubling factor so we don't accidentally see scale as unchanged below + scale = scale || ".5"; - // Update scale, tolerating zero or NaN from tween.cur() - // And breaking the loop if scale is unchanged or perfect, or if we've just had enough - } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); - } + // Adjust and apply + start = start / scale; + jQuery.style( tween.elem, prop, start + unit ); - tween.unit = unit; - tween.start = start; - // If a +=/-= token was provided, we're doing a relative animation - tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end; + // Update scale, tolerating zero or NaN from tween.cur() + // And breaking the loop if scale is unchanged or perfect, or if we've just had enough + } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); } + + // Update tween properties + if ( parts ) { + tween.unit = unit; + tween.start = +start || +target || 0; + // If a +=/-= token was provided, we're doing a relative animation + tween.end = parts[ 1 ] ? + start + ( parts[ 1 ] + 1 ) * parts[ 2 ] : + +parts[ 2 ]; + } + return tween; }] }; @@ -55,19 +62,18 @@ function createFxNow() { return ( fxNow = jQuery.now() ); } -function createTweens( animation, props ) { - jQuery.each( props, function( prop, value ) { - var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( collection[ index ].call( animation, prop, value ) ) { +function createTween( value, prop, animation ) { + var tween, + collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( (tween = collection[ index ].call( animation, prop, value )) ) { - // we're done with this property - return; - } + // we're done with this property + return tween; } - }); + } } function Animation( elem, properties, options ) { @@ -153,7 +159,7 @@ function Animation( elem, properties, options ) { } } - createTweens( animation, props ); + jQuery.map( props, createTween, animation ); if ( jQuery.isFunction( animation.opts.start ) ) { animation.opts.start.call( elem, animation ); @@ -243,12 +249,12 @@ jQuery.Animation = jQuery.extend( Animation, { function defaultPrefilter( elem, props, opts ) { /* jshint validthis: true */ - var index, prop, value, length, dataShow, toggle, tween, hooks, oldfire, + var index, prop, value, toggle, tween, hooks, oldfire, anim = this, - style = elem.style, orig = {}, - handled = [], - hidden = elem.nodeType && isHidden( elem ); + style = elem.style, + hidden = elem.nodeType && isHidden( elem ), + dataShow = data_priv.get( elem, "fxshow" ); // handle queue: false promises if ( !opts.queue ) { @@ -304,7 +310,6 @@ function defaultPrefilter( elem, props, opts ) { // show/hide pass - dataShow = data_priv.get( elem, "fxshow" ); for ( index in props ) { value = props[ index ]; if ( rfxtypes.exec( value ) ) { @@ -313,21 +318,23 @@ function defaultPrefilter( elem, props, opts ) { if ( value === ( hidden ? "hide" : "show" ) ) { // 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 !== undefined && dataShow[ index ] !== undefined ) { + if ( value === "show" && dataShow && dataShow[ index ] !== undefined ) { hidden = true; } else { continue; } } - handled.push( index ); + orig[ index ] = dataShow && dataShow[ index ] || jQuery.style( elem, index ); } } - length = handled.length; - if ( length ) { - dataShow = data_priv.get( elem, "fxshow" ) || data_priv.access( elem, "fxshow", {} ); - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; + if ( !jQuery.isEmptyObject( orig ) ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = data_priv.access( elem, "fxshow", {} ); } // store state if its toggle - enables .stop().toggle() to "reverse" @@ -349,10 +356,8 @@ function defaultPrefilter( elem, props, opts ) { jQuery.style( elem, prop, orig[ prop ] ); } }); - for ( index = 0 ; index < length ; index++ ) { - prop = handled[ index ]; - tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 ); - orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop ); + for ( prop in orig ) { + tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); if ( !( prop in dataShow ) ) { dataShow[ prop ] = tween.start; diff --git a/test/unit/effects.js b/test/unit/effects.js index 1b2bee85a..47bc9b4b6 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -1371,6 +1371,44 @@ test("Do not append px to 'fill-opacity' #9548", 1, function() { }); }); +test("line-height animates correctly (#13855)", function() { + expect( 12 ); + stop(); + + var + animated = jQuery( + "

unitless

" + + "

px

" + + "

percent

" + + "

em

" + ).appendTo("#qunit-fixture"), + initialHeight = jQuery.map( animated, function( el ) { + return jQuery( el ).height(); + }); + + animated.animate( { "line-height": "hide" }, 1500 ); + setTimeout(function() { + animated.each(function( i ) { + var label = jQuery.text( this ), + initial = initialHeight[ i ], + height = jQuery( this ).height(); + ok( height < initial, "hide " + label + ": upper bound" ); + ok( height > initial / 2, "hide " + label + ": lower bound" ); + }); + animated.stop( true, true ).hide().animate( { "line-height": "show" }, 1500 ); + setTimeout(function() { + animated.each(function( i ) { + var label = jQuery.text( this ), + initial = initialHeight[ i ], + height = jQuery( this ).height(); + ok( height < initial / 2, "show " + label + ": upper bound" ); + }); + animated.stop( true, true ); + start(); + }, 400 ); + }, 400 ); +}); + // Start 1.8 Animation tests asyncTest( "jQuery.Animation( object, props, opts )", 4, function() { var animation,