]> source.dussan.org Git - jquery.git/commitdiff
Dimensions: Include scroll gutter in "padding" box
authorRichard Gibson <richard.gibson@gmail.com>
Mon, 19 Jun 2017 19:42:55 +0000 (15:42 -0400)
committerGitHub <noreply@github.com>
Mon, 19 Jun 2017 19:42:55 +0000 (15:42 -0400)
Fixes gh-3589
Closes gh-3656

src/css.js
test/unit/dimensions.js

index 24c5c7327283ed8ce9df5a744e2a2e0afc04f08b..5da9a3aea02701e1f5c0e165adf8f836ef2eb3e6 100644 (file)
@@ -80,58 +80,77 @@ function setPositiveNumber( elem, value, subtract ) {
                value;
 }
 
-function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
-       var i,
-               val = 0;
-
-       // If we already have the right measurement, avoid augmentation
-       if ( extra === ( isBorderBox ? "border" : "content" ) ) {
-               i = 4;
-
-       // Otherwise initialize for horizontal or vertical properties
-       } else {
-               i = name === "width" ? 1 : 0;
+function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
+       var i = dimension === "width" ? 1 : 0,
+               extra = 0,
+               delta = 0;
+
+       // Adjustment may not be necessary
+       if ( box === ( isBorderBox ? "border" : "content" ) ) {
+               return 0;
        }
 
        for ( ; i < 4; i += 2 ) {
 
-               // Both box models exclude margin, so add it if we want it
-               if ( extra === "margin" ) {
-                       val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+               // Both box models exclude margin
+               if ( box === "margin" ) {
+                       delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
                }
 
-               if ( isBorderBox ) {
+               // If we get here with a content-box, we're seeking "padding" or "border" or "margin"
+               if ( !isBorderBox ) {
 
-                       // border-box includes padding, so remove it if we want content
-                       if ( extra === "content" ) {
-                               val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
-                       }
+                       // Add padding
+                       delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
 
-                       // At this point, extra isn't border nor margin, so remove border
-                       if ( extra !== "margin" ) {
-                               val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+                       // For "border" or "margin", add border
+                       if ( box !== "padding" ) {
+                               delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+
+                       // But still keep track of it otherwise
+                       } else {
+                               extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
                        }
+
+               // If we get here with a border-box (content + padding + border), we're seeking "content" or
+               // "padding" or "margin"
                } else {
 
-                       // At this point, extra isn't content, so add padding
-                       val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+                       // For "content", subtract padding
+                       if ( box === "content" ) {
+                               delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+                       }
 
-                       // At this point, extra isn't content nor padding, so add border
-                       if ( extra !== "padding" ) {
-                               val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+                       // For "content" or "padding", subtract border
+                       if ( box !== "margin" ) {
+                               delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
                        }
                }
        }
 
-       return val;
+       // Account for positive content-box scroll gutter when requested by providing computedVal
+       if ( !isBorderBox && computedVal >= 0 ) {
+
+               // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
+               // Assuming integer scroll gutter, subtract the rest and round down
+               delta += Math.max( 0, Math.ceil(
+                       elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
+                       computedVal -
+                       delta -
+                       extra -
+                       0.5
+               ) );
+       }
+
+       return delta;
 }
 
-function getWidthOrHeight( elem, name, extra ) {
+function getWidthOrHeight( elem, dimension, extra ) {
 
        // Start with computed style
        var valueIsBorderBox,
                styles = getStyles( elem ),
-               val = curCSS( elem, name, styles ),
+               val = curCSS( elem, dimension, styles ),
                isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
 
        // Computed unit is not pixels. Stop here and return.
@@ -142,25 +161,28 @@ function getWidthOrHeight( elem, name, extra ) {
        // Check for style in case a browser which returns unreliable values
        // for getComputedStyle silently falls back to the reliable elem.style
        valueIsBorderBox = isBorderBox &&
-               ( support.boxSizingReliable() || val === elem.style[ name ] );
+               ( support.boxSizingReliable() || val === elem.style[ dimension ] );
 
        // Fall back to offsetWidth/Height when value is "auto"
        // This happens for inline elements with no explicit setting (gh-3571)
        if ( val === "auto" ) {
-               val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ];
+               val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ];
        }
 
-       // Normalize "", auto, and prepare for extra
+       // Normalize "" and auto
        val = parseFloat( val ) || 0;
 
-       // Use the active box-sizing model to add/subtract irrelevant styles
+       // Adjust for the element's box model
        return ( val +
-               augmentWidthOrHeight(
+               boxModelAdjustment(
                        elem,
-                       name,
+                       dimension,
                        extra || ( isBorderBox ? "border" : "content" ),
                        valueIsBorderBox,
-                       styles
+                       styles,
+
+                       // Provide the current computed size to request scroll gutter calculation (gh-3589)
+                       val
                )
        ) + "px";
 }
@@ -319,8 +341,8 @@ jQuery.extend( {
        }
 } );
 
-jQuery.each( [ "height", "width" ], function( i, name ) {
-       jQuery.cssHooks[ name ] = {
+jQuery.each( [ "height", "width" ], function( i, dimension ) {
+       jQuery.cssHooks[ dimension ] = {
                get: function( elem, computed, extra ) {
                        if ( computed ) {
 
@@ -336,18 +358,18 @@ jQuery.each( [ "height", "width" ], function( i, name ) {
                                        // in IE throws an error.
                                        ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
                                                swap( elem, cssShow, function() {
-                                                       return getWidthOrHeight( elem, name, extra );
+                                                       return getWidthOrHeight( elem, dimension, extra );
                                                } ) :
-                                               getWidthOrHeight( elem, name, extra );
+                                               getWidthOrHeight( elem, dimension, extra );
                        }
                },
 
                set: function( elem, value, extra ) {
                        var matches,
-                               styles = extra && getStyles( elem ),
-                               subtract = extra && augmentWidthOrHeight(
+                               styles = getStyles( elem ),
+                               subtract = extra && boxModelAdjustment(
                                        elem,
-                                       name,
+                                       dimension,
                                        extra,
                                        jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
                                        styles
@@ -357,8 +379,8 @@ jQuery.each( [ "height", "width" ], function( i, name ) {
                        if ( subtract && ( matches = rcssNum.exec( value ) ) &&
                                ( matches[ 3 ] || "px" ) !== "px" ) {
 
-                               elem.style[ name ] = value;
-                               value = jQuery.css( elem, name );
+                               elem.style[ dimension ] = value;
+                               value = jQuery.css( elem, dimension );
                        }
 
                        return setPositiveNumber( elem, value, subtract );
index 76850e9e2574b8d341ef8093c736471e7d425591..2ec5f36832b0ac84338c43eac7f125d0a3dfe194 100644 (file)
@@ -544,4 +544,87 @@ QUnit.test( "width/height on an inline element with no explicitly-set dimensions
        } );
 } );
 
+QUnit.test( "interaction with scrollbars (gh-3589)", function( assert ) {
+       assert.expect( 36 );
+
+       var i,
+               suffix = "",
+               updater = function( adjustment ) {
+                       return function( i, old ) {
+                               return old + adjustment;
+                       };
+               },
+               parent = jQuery( "<div/>" )
+                       .css( { position: "absolute", width: "1000px", height: "1000px" } )
+                       .appendTo( "#qunit-fixture" ),
+               fraction = jQuery( "<div style='width:4.5px;'/>" ).appendTo( parent ).width() % 1,
+               borderWidth = 1,
+               padding = 2,
+               size = 100 + fraction,
+               scrollBox = {
+                       position: "absolute",
+                       overflow: "scroll",
+                       width: size + "px",
+                       height: size + "px"
+               },
+               borderBox = {
+                       border: borderWidth + "px solid blue",
+                       padding: padding + "px"
+               },
+               plainContentBox = jQuery( "<div />" )
+                       .css( scrollBox )
+                       .css( { "box-sizing": "content-box" } )
+                       .appendTo( parent ),
+               contentBox = jQuery( "<div />" )
+                       .css( scrollBox )
+                       .css( borderBox )
+                       .css( { "box-sizing": "content-box" } )
+                       .appendTo( parent ),
+               borderBox = jQuery( "<div />" )
+                       .css( scrollBox )
+                       .css( borderBox )
+                       .css( { "box-sizing": "border-box" } )
+                       .appendTo( parent ),
+               $boxes = jQuery( [ plainContentBox[ 0 ], contentBox[ 0 ], borderBox[ 0 ] ] );
+
+       for ( i = 0; i < 3; i++ ) {
+               if ( i === 1 ) {
+                       suffix = " after increasing inner* by " + i;
+                       size += i;
+                       $boxes.innerWidth( updater( i ) ).innerHeight( updater( i ) );
+               } else if ( i === 2 ) {
+                       suffix = " after increasing outer* by " + i;
+                       size += i;
+                       $boxes.outerWidth( updater( i ) ).outerHeight( updater( i ) );
+               }
+
+               assert.equal( plainContentBox.innerWidth(), size,
+                       "plain content-box innerWidth includes scroll gutter" + suffix );
+               assert.equal( plainContentBox.innerHeight(), size,
+                       "plain content-box innerHeight includes scroll gutter" + suffix );
+               assert.equal( plainContentBox.outerWidth(), size,
+                       "plain content-box outerWidth includes scroll gutter" + suffix );
+               assert.equal( plainContentBox.outerHeight(), size,
+                       "plain content-box outerHeight includes scroll gutter" + suffix );
+
+               assert.equal( contentBox.innerWidth(), size + 2 * padding,
+                       "content-box innerWidth includes scroll gutter" + suffix );
+               assert.equal( contentBox.innerHeight(), size + 2 * padding,
+                       "content-box innerHeight includes scroll gutter" + suffix );
+               assert.equal( contentBox.outerWidth(), size + 2 * padding + 2 * borderWidth,
+                       "content-box outerWidth includes scroll gutter" + suffix );
+               assert.equal( contentBox.outerHeight(), size + 2 * padding + 2 * borderWidth,
+                       "content-box outerHeight includes scroll gutter" + suffix );
+
+               assert.equal( borderBox.innerWidth(), size - 2 * borderWidth,
+                       "border-box innerWidth includes scroll gutter" + suffix );
+               assert.equal( borderBox.innerHeight(), size - 2 * borderWidth,
+                       "border-box innerHeight includes scroll gutter" + suffix );
+               assert.equal( borderBox.outerWidth(), size,
+                       "border-box outerWidth includes scroll gutter" + suffix );
+               assert.equal( borderBox.outerHeight(), size,
+                       "border-box outerHeight includes scroll gutter" + suffix );
+       }
+} );
+
 } )();