From 6fb2cefc602cf8bd8b85373f480e978bb8978e37 Mon Sep 17 00:00:00 2001 From: Mr21 Date: Wed, 4 Feb 2015 14:10:14 +0100 Subject: [PATCH] CSS: Support relative adjustment in any applicable unit Fixes gh-1711 Closes gh-2011 (cherry picked from commit 9b03f6df88a8d9dbda3f7893cdd84e3a3c70da17) Conflicts: src/css.js src/effects.js --- src/css.js | 19 +++++++------- src/css/adjustCSS.js | 61 ++++++++++++++++++++++++++++++++++++++++++++ src/effects.js | 57 ++++------------------------------------- src/var/rcssNum.js | 7 +++++ test/unit/css.js | 52 +++++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 61 deletions(-) create mode 100644 src/css/adjustCSS.js create mode 100644 src/var/rcssNum.js diff --git a/src/css.js b/src/css.js index a12a87408..51cc297e6 100644 --- a/src/css.js +++ b/src/css.js @@ -3,10 +3,12 @@ define([ "./var/pnum", "./core/access", "./css/var/rmargin", + "./var/rcssNum", "./css/var/rnumnonpx", "./css/var/cssExpand", "./css/var/isHidden", "./css/curCSS", + "./css/adjustCSS", "./css/defaultDisplay", "./css/addGetHookIf", "./css/support", @@ -15,8 +17,8 @@ define([ "./css/swap", "./core/ready", "./selector" // contains -], function( jQuery, pnum, access, rmargin, rnumnonpx, cssExpand, isHidden, - curCSS, defaultDisplay, addGetHookIf, support ) { +], function( jQuery, pnum, access, rmargin, rcssNum, rnumnonpx, cssExpand, isHidden, + curCSS, adjustCSS, defaultDisplay, addGetHookIf, support ) { var // BuildExclude @@ -30,7 +32,6 @@ var // https://developer.mozilla.org/en-US/docs/CSS/display rdisplayswap = /^(none|table(?!-c[ea]).+)/, rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ), - rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ), cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { @@ -273,9 +274,9 @@ jQuery.extend({ if ( value !== undefined ) { type = typeof value; - // convert relative number strings (+= or -=) to relative numbers. #7345 - if ( type === "string" && (ret = rrelNum.exec( value )) ) { - value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && (ret = rcssNum.exec( value )) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); // Fixes bug #9237 type = "number"; } @@ -285,9 +286,9 @@ jQuery.extend({ return; } - // If a number was passed in, add 'px' (except for certain CSS properties) - if ( type === "number" && !jQuery.cssNumber[ origName ] ) { - value += "px"; + // If a number was passed in, add the unit (except for certain CSS properties) + if ( type === "number" ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, diff --git a/src/css/adjustCSS.js b/src/css/adjustCSS.js new file mode 100644 index 000000000..05fddd15b --- /dev/null +++ b/src/css/adjustCSS.js @@ -0,0 +1,61 @@ +define([ + "../core", + "../var/rcssNum" +], function( jQuery, rcssNum ) { + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, + scale = 1, + maxIterations = 20, + currentValue = tween ? + function() { return tween.cur(); } : + function() { return jQuery.css( elem, prop, "" ); }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + // Starting value computation is required for potential unit mismatches + initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + do { + // If previous iteration zeroed out, double until we get *something*. + // Use string for doubling so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + initialInUnit = initialInUnit / scale; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // Break the loop if scale is unchanged or perfect, or if we've just had enough. + } while ( + scale !== (scale = currentValue() / initial) && scale !== 1 && --maxIterations + ); + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + +return adjustCSS; +}); diff --git a/src/effects.js b/src/effects.js index 42a9684b8..993ebb804 100644 --- a/src/effects.js +++ b/src/effects.js @@ -1,8 +1,9 @@ define([ "./core", - "./var/pnum", + "./var/rcssNum", "./css/var/cssExpand", "./css/var/isHidden", + "./css/adjustCSS", "./css/defaultDisplay", "./core/init", @@ -11,65 +12,17 @@ define([ "./css", "./deferred", "./traversing" -], function( jQuery, pnum, cssExpand, isHidden, defaultDisplay ) { +], function( jQuery, rcssNum, cssExpand, isHidden, adjustCSS, defaultDisplay ) { var fxNow, timerId, rfxtypes = /^(?:toggle|show|hide)$/, - rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ), rrun = /queueHooks$/, animationPrefilters = [ defaultPrefilter ], tweeners = { "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ), - target = tween.cur(), - 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 ( start && start[ 3 ] !== unit ) { - // Trust units reported by jQuery.css - unit = unit || start[ 3 ]; - - // Make sure we update the tween properties later on - parts = parts || []; - - // Iteratively approximate from a nonzero starting point - start = +target || 1; - - 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"; - - // Adjust and apply - start = start / scale; - jQuery.style( tween.elem, prop, start + unit ); - - // 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 ) { - start = tween.start = +start || +target || 0; - tween.unit = unit; - // If a +=/-= token was provided, we're doing a relative animation - tween.end = parts[ 1 ] ? - start + ( parts[ 1 ] + 1 ) * parts[ 2 ] : - +parts[ 2 ]; - } - + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); return tween; } ] }; diff --git a/src/var/rcssNum.js b/src/var/rcssNum.js new file mode 100644 index 000000000..2fc3938a5 --- /dev/null +++ b/src/var/rcssNum.js @@ -0,0 +1,7 @@ +define([ + "../var/pnum" +], function( pnum ) { + +return new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + +}); diff --git a/test/unit/css.js b/test/unit/css.js index ffe607b15..9dcc9101d 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -217,6 +217,58 @@ test( "css() explicit and relative values", 29, function() { equal( $elem.css("opacity"), "1", "'+=0.5' on opacity (params)" ); }); +test( "css() non-px relative values (gh-1711)", 17, function() { + var cssCurrent, + units = {}, + $child = jQuery( "#nothiddendivchild" ), + add = function( prop, val, unit ) { + var str = ( val < 0 ? "-=" : "+=" ) + Math.abs( val ) + unit; + $child.css( prop, str ); + equal( + Math.round( parseFloat( $child.css( prop ) ) ), + Math.round( cssCurrent += val * units[ prop ][ unit ] ), + prop + ": '" + str + "'" + ); + }, + getUnits = function( prop ) { + units[ prop ] = { + "px": 1, + "em": parseFloat( $child.css( prop, "100em" ).css( prop ) ) / 100, + "pt": parseFloat( $child.css( prop, "100pt" ).css( prop ) ) / 100, + "pc": parseFloat( $child.css( prop, "100pc" ).css( prop ) ) / 100, + "cm": parseFloat( $child.css( prop, "100cm" ).css( prop ) ) / 100, + "mm": parseFloat( $child.css( prop, "100mm" ).css( prop ) ) / 100, + "%" : parseFloat( $child.css( prop, "100%" ).css( prop ) ) / 100 + }; + }; + + jQuery( "#nothiddendiv" ).css({ height: 1, padding: 0, width: 400 }); + $child.css({ height: 1, padding: 0 }); + + getUnits( "width" ); + cssCurrent = parseFloat( $child.css( "width", "50%" ).css( "width" ) ); + add( "width", 25, "%" ); + add( "width", -50, "%" ); + add( "width", 10, "em" ); + add( "width", 10, "pt" ); + add( "width", -2.3, "pt" ); + add( "width", 5, "pc" ); + add( "width", -5, "em" ); + add( "width", +2, "cm" ); + add( "width", -15, "mm" ); + add( "width", 21, "px" ); + + getUnits( "lineHeight" ); + cssCurrent = parseFloat( $child.css( "lineHeight", "1em" ).css( "lineHeight" ) ); + add( "lineHeight", 2, "em" ); + add( "lineHeight", -10, "px" ); + add( "lineHeight", 20, "pt" ); + add( "lineHeight", 30, "pc" ); + add( "lineHeight", 1, "cm" ); + add( "lineHeight", -20, "mm" ); + add( "lineHeight", 50, "%" ); +}); + test("css(String, Object)", function() { expect( 19 ); var j, div, display, ret, success; -- 2.39.5