Dimensions: Include scroll gutter in "padding" box

Fixes gh-3589
Closes gh-3656
This commit is contained in:
Richard Gibson 2017-06-19 15:42:55 -04:00 committed by GitHub
parent 5bdd1ca1ab
commit 80f1c8239e
2 changed files with 150 additions and 45 deletions

View File

@ -80,58 +80,77 @@ function setPositiveNumber( elem, value, subtract ) {
value; value;
} }
function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
var i, var i = dimension === "width" ? 1 : 0,
val = 0; extra = 0,
delta = 0;
// If we already have the right measurement, avoid augmentation // Adjustment may not be necessary
if ( extra === ( isBorderBox ? "border" : "content" ) ) { if ( box === ( isBorderBox ? "border" : "content" ) ) {
i = 4; return 0;
// Otherwise initialize for horizontal or vertical properties
} else {
i = name === "width" ? 1 : 0;
} }
for ( ; i < 4; i += 2 ) { for ( ; i < 4; i += 2 ) {
// Both box models exclude margin, so add it if we want it // Both box models exclude margin
if ( extra === "margin" ) { if ( box === "margin" ) {
val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); 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 // Add padding
if ( extra === "content" ) { delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
val -= jQuery.css( elem, "padding" + cssExpand[ i ], 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 );
} }
// At this point, extra isn't border nor margin, so remove border // If we get here with a border-box (content + padding + border), we're seeking "content" or
if ( extra !== "margin" ) { // "padding" or "margin"
val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
}
} else { } else {
// At this point, extra isn't content, so add padding // For "content", subtract padding
val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); if ( box === "content" ) {
delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
}
// At this point, extra isn't content nor padding, so add border // For "content" or "padding", subtract border
if ( extra !== "padding" ) { if ( box !== "margin" ) {
val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); 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 // Start with computed style
var valueIsBorderBox, var valueIsBorderBox,
styles = getStyles( elem ), styles = getStyles( elem ),
val = curCSS( elem, name, styles ), val = curCSS( elem, dimension, styles ),
isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
// Computed unit is not pixels. Stop here and return. // 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 // Check for style in case a browser which returns unreliable values
// for getComputedStyle silently falls back to the reliable elem.style // for getComputedStyle silently falls back to the reliable elem.style
valueIsBorderBox = isBorderBox && valueIsBorderBox = isBorderBox &&
( support.boxSizingReliable() || val === elem.style[ name ] ); ( support.boxSizingReliable() || val === elem.style[ dimension ] );
// Fall back to offsetWidth/Height when value is "auto" // Fall back to offsetWidth/Height when value is "auto"
// This happens for inline elements with no explicit setting (gh-3571) // This happens for inline elements with no explicit setting (gh-3571)
if ( val === "auto" ) { 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; val = parseFloat( val ) || 0;
// Use the active box-sizing model to add/subtract irrelevant styles // Adjust for the element's box model
return ( val + return ( val +
augmentWidthOrHeight( boxModelAdjustment(
elem, elem,
name, dimension,
extra || ( isBorderBox ? "border" : "content" ), extra || ( isBorderBox ? "border" : "content" ),
valueIsBorderBox, valueIsBorderBox,
styles styles,
// Provide the current computed size to request scroll gutter calculation (gh-3589)
val
) )
) + "px"; ) + "px";
} }
@ -319,8 +341,8 @@ jQuery.extend( {
} }
} ); } );
jQuery.each( [ "height", "width" ], function( i, name ) { jQuery.each( [ "height", "width" ], function( i, dimension ) {
jQuery.cssHooks[ name ] = { jQuery.cssHooks[ dimension ] = {
get: function( elem, computed, extra ) { get: function( elem, computed, extra ) {
if ( computed ) { if ( computed ) {
@ -336,18 +358,18 @@ jQuery.each( [ "height", "width" ], function( i, name ) {
// in IE throws an error. // in IE throws an error.
( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
swap( elem, cssShow, function() { 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 ) { set: function( elem, value, extra ) {
var matches, var matches,
styles = extra && getStyles( elem ), styles = getStyles( elem ),
subtract = extra && augmentWidthOrHeight( subtract = extra && boxModelAdjustment(
elem, elem,
name, dimension,
extra, extra,
jQuery.css( elem, "boxSizing", false, styles ) === "border-box", jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
styles styles
@ -357,8 +379,8 @@ jQuery.each( [ "height", "width" ], function( i, name ) {
if ( subtract && ( matches = rcssNum.exec( value ) ) && if ( subtract && ( matches = rcssNum.exec( value ) ) &&
( matches[ 3 ] || "px" ) !== "px" ) { ( matches[ 3 ] || "px" ) !== "px" ) {
elem.style[ name ] = value; elem.style[ dimension ] = value;
value = jQuery.css( elem, name ); value = jQuery.css( elem, dimension );
} }
return setPositiveNumber( elem, value, subtract ); return setPositiveNumber( elem, value, subtract );

View 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 );
}
} );
} )(); } )();