From 80f1c8239e9d5f793f3e54e0cb8d7bd0747e4856 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Mon, 19 Jun 2017 15:42:55 -0400 Subject: [PATCH] Dimensions: Include scroll gutter in "padding" box Fixes gh-3589 Closes gh-3656 --- src/css.js | 114 ++++++++++++++++++++++++---------------- test/unit/dimensions.js | 83 +++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 46 deletions(-) diff --git a/src/css.js b/src/css.js index 24c5c7327..5da9a3aea 100644 --- a/src/css.js +++ b/src/css.js @@ -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 ); diff --git a/test/unit/dimensions.js b/test/unit/dimensions.js index 76850e9e2..2ec5f3683 100644 --- a/test/unit/dimensions.js +++ b/test/unit/dimensions.js @@ -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( "
" ) + .css( { position: "absolute", width: "1000px", height: "1000px" } ) + .appendTo( "#qunit-fixture" ), + fraction = jQuery( "
" ).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( "
" ) + .css( scrollBox ) + .css( { "box-sizing": "content-box" } ) + .appendTo( parent ), + contentBox = jQuery( "
" ) + .css( scrollBox ) + .css( borderBox ) + .css( { "box-sizing": "content-box" } ) + .appendTo( parent ), + borderBox = jQuery( "
" ) + .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 ); + } +} ); + } )(); -- 2.39.5