Dimensions: avoid fetching boxSizing when setting width/height
[jquery.git] / src / css.js
blobea730201219c0dfa20cedf7535657f48da81d0f2
1 define( [
2 "./core",
3 "./var/pnum",
4 "./core/access",
5 "./core/camelCase",
6 "./var/document",
7 "./var/rcssNum",
8 "./css/var/rnumnonpx",
9 "./css/var/cssExpand",
10 "./css/var/getStyles",
11 "./css/var/swap",
12 "./css/curCSS",
13 "./css/adjustCSS",
14 "./css/addGetHookIf",
15 "./css/support",
16 "./css/finalPropName",
18 "./core/init",
19 "./core/ready",
20 "./selector" // contains
21 ], function( jQuery, pnum, access, camelCase, document, rcssNum, rnumnonpx, cssExpand,
22 getStyles, swap, curCSS, adjustCSS, addGetHookIf, support, finalPropName ) {
24 "use strict";
26 var
28 // Swappable if display is none or starts with table
29 // except "table", "table-cell", or "table-caption"
30 // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
31 rdisplayswap = /^(none|table(?!-c[ea]).+)/,
32 rcustomProp = /^--/,
33 cssShow = { position: "absolute", visibility: "hidden", display: "block" },
34 cssNormalTransform = {
35 letterSpacing: "0",
36 fontWeight: "400"
39 function setPositiveNumber( elem, value, subtract ) {
41 // Any relative (+/-) values have already been
42 // normalized at this point
43 var matches = rcssNum.exec( value );
44 return matches ?
46 // Guard against undefined "subtract", e.g., when used as in cssHooks
47 Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
48 value;
51 function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
52 var i = dimension === "width" ? 1 : 0,
53 extra = 0,
54 delta = 0;
56 // Adjustment may not be necessary
57 if ( box === ( isBorderBox ? "border" : "content" ) ) {
58 return 0;
61 for ( ; i < 4; i += 2 ) {
63 // Both box models exclude margin
64 if ( box === "margin" ) {
65 delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
68 // If we get here with a content-box, we're seeking "padding" or "border" or "margin"
69 if ( !isBorderBox ) {
71 // Add padding
72 delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
74 // For "border" or "margin", add border
75 if ( box !== "padding" ) {
76 delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
78 // But still keep track of it otherwise
79 } else {
80 extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
83 // If we get here with a border-box (content + padding + border), we're seeking "content" or
84 // "padding" or "margin"
85 } else {
87 // For "content", subtract padding
88 if ( box === "content" ) {
89 delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
92 // For "content" or "padding", subtract border
93 if ( box !== "margin" ) {
94 delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
99 // Account for positive content-box scroll gutter when requested by providing computedVal
100 if ( !isBorderBox && computedVal >= 0 ) {
102 // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
103 // Assuming integer scroll gutter, subtract the rest and round down
104 delta += Math.max( 0, Math.ceil(
105 elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
106 computedVal -
107 delta -
108 extra -
110 ) );
113 return delta;
116 function getWidthOrHeight( elem, dimension, extra ) {
118 // Start with computed style
119 var styles = getStyles( elem ),
120 val = curCSS( elem, dimension, styles ),
121 isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
122 valueIsBorderBox = isBorderBox;
124 // Support: Firefox <=54
125 // Return a confounding non-pixel value or feign ignorance, as appropriate.
126 if ( rnumnonpx.test( val ) ) {
127 if ( !extra ) {
128 return val;
130 val = "auto";
133 // Check for style in case a browser which returns unreliable values
134 // for getComputedStyle silently falls back to the reliable elem.style
135 valueIsBorderBox = valueIsBorderBox &&
136 ( support.boxSizingReliable() || val === elem.style[ dimension ] );
138 // Fall back to offsetWidth/offsetHeight when value is "auto"
139 // This happens for inline elements with no explicit setting (gh-3571)
140 // Support: Android <=4.1 - 4.3 only
141 // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
142 if ( val === "auto" ||
143 !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) {
145 val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ];
147 // offsetWidth/offsetHeight provide border-box values
148 valueIsBorderBox = true;
151 // Normalize "" and auto
152 val = parseFloat( val ) || 0;
154 // Adjust for the element's box model
155 return ( val +
156 boxModelAdjustment(
157 elem,
158 dimension,
159 extra || ( isBorderBox ? "border" : "content" ),
160 valueIsBorderBox,
161 styles,
163 // Provide the current computed size to request scroll gutter calculation (gh-3589)
166 ) + "px";
169 jQuery.extend( {
171 // Add in style property hooks for overriding the default
172 // behavior of getting and setting a style property
173 cssHooks: {
174 opacity: {
175 get: function( elem, computed ) {
176 if ( computed ) {
178 // We should always get a number back from opacity
179 var ret = curCSS( elem, "opacity" );
180 return ret === "" ? "1" : ret;
186 // Don't automatically add "px" to these possibly-unitless properties
187 cssNumber: {
188 "animationIterationCount": true,
189 "columnCount": true,
190 "fillOpacity": true,
191 "flexGrow": true,
192 "flexShrink": true,
193 "fontWeight": true,
194 "lineHeight": true,
195 "opacity": true,
196 "order": true,
197 "orphans": true,
198 "widows": true,
199 "zIndex": true,
200 "zoom": true
203 // Add in properties whose names you wish to fix before
204 // setting or getting the value
205 cssProps: {},
207 // Get and set the style property on a DOM Node
208 style: function( elem, name, value, extra ) {
210 // Don't set styles on text and comment nodes
211 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
212 return;
215 // Make sure that we're working with the right name
216 var ret, type, hooks,
217 origName = camelCase( name ),
218 isCustomProp = rcustomProp.test( name ),
219 style = elem.style;
221 // Make sure that we're working with the right name. We don't
222 // want to query the value if it is a CSS custom property
223 // since they are user-defined.
224 if ( !isCustomProp ) {
225 name = finalPropName( origName );
228 // Gets hook for the prefixed version, then unprefixed version
229 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
231 // Check if we're setting a value
232 if ( value !== undefined ) {
233 type = typeof value;
235 // Convert "+=" or "-=" to relative numbers (#7345)
236 if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
237 value = adjustCSS( elem, name, ret );
239 // Fixes bug #9237
240 type = "number";
243 // Make sure that null and NaN values aren't set (#7116)
244 if ( value == null || value !== value ) {
245 return;
248 // If a number was passed in, add the unit (except for certain CSS properties)
249 if ( type === "number" ) {
250 value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
253 // background-* props affect original clone's values
254 if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
255 style[ name ] = "inherit";
258 // If a hook was provided, use that value, otherwise just set the specified value
259 if ( !hooks || !( "set" in hooks ) ||
260 ( value = hooks.set( elem, value, extra ) ) !== undefined ) {
262 if ( isCustomProp ) {
263 style.setProperty( name, value );
264 } else {
265 style[ name ] = value;
269 } else {
271 // If a hook was provided get the non-computed value from there
272 if ( hooks && "get" in hooks &&
273 ( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
275 return ret;
278 // Otherwise just get the value from the style object
279 return style[ name ];
283 css: function( elem, name, extra, styles ) {
284 var val, num, hooks,
285 origName = camelCase( name ),
286 isCustomProp = rcustomProp.test( name );
288 // Make sure that we're working with the right name. We don't
289 // want to modify the value if it is a CSS custom property
290 // since they are user-defined.
291 if ( !isCustomProp ) {
292 name = finalPropName( origName );
295 // Try prefixed name followed by the unprefixed name
296 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
298 // If a hook was provided get the computed value from there
299 if ( hooks && "get" in hooks ) {
300 val = hooks.get( elem, true, extra );
303 // Otherwise, if a way to get the computed value exists, use that
304 if ( val === undefined ) {
305 val = curCSS( elem, name, styles );
308 // Convert "normal" to computed value
309 if ( val === "normal" && name in cssNormalTransform ) {
310 val = cssNormalTransform[ name ];
313 // Make numeric if forced or a qualifier was provided and val looks numeric
314 if ( extra === "" || extra ) {
315 num = parseFloat( val );
316 return extra === true || isFinite( num ) ? num || 0 : val;
319 return val;
321 } );
323 jQuery.each( [ "height", "width" ], function( i, dimension ) {
324 jQuery.cssHooks[ dimension ] = {
325 get: function( elem, computed, extra ) {
326 if ( computed ) {
328 // Certain elements can have dimension info if we invisibly show them
329 // but it must have a current display style that would benefit
330 return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
332 // Support: Safari 8+
333 // Table columns in Safari have non-zero offsetWidth & zero
334 // getBoundingClientRect().width unless display is changed.
335 // Support: IE <=11 only
336 // Running getBoundingClientRect on a disconnected node
337 // in IE throws an error.
338 ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
339 swap( elem, cssShow, function() {
340 return getWidthOrHeight( elem, dimension, extra );
341 } ) :
342 getWidthOrHeight( elem, dimension, extra );
346 set: function( elem, value, extra ) {
347 var matches,
348 styles = getStyles( elem ),
349 scrollBoxSize = support.scrollboxSize() === styles.position,
351 // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
352 boxSizingNeeded = scrollBoxSize || extra,
353 isBorderBox = boxSizingNeeded &&
354 jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
355 subtract = extra && boxModelAdjustment(
356 elem,
357 dimension,
358 extra,
359 isBorderBox,
360 styles
363 // Account for unreliable border-box dimensions by comparing offset* to computed and
364 // faking a content-box to get border and padding (gh-3699)
365 if ( isBorderBox && scrollBoxSize ) {
366 subtract -= Math.ceil(
367 elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
368 parseFloat( styles[ dimension ] ) -
369 boxModelAdjustment( elem, dimension, "border", false, styles ) -
374 // Convert to pixels if value adjustment is needed
375 if ( subtract && ( matches = rcssNum.exec( value ) ) &&
376 ( matches[ 3 ] || "px" ) !== "px" ) {
378 elem.style[ dimension ] = value;
379 value = jQuery.css( elem, dimension );
382 return setPositiveNumber( elem, value, subtract );
385 } );
387 jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
388 function( elem, computed ) {
389 if ( computed ) {
390 return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
391 elem.getBoundingClientRect().left -
392 swap( elem, { marginLeft: 0 }, function() {
393 return elem.getBoundingClientRect().left;
395 ) + "px";
400 // These hooks are used by animate to expand properties
401 jQuery.each( {
402 margin: "",
403 padding: "",
404 border: "Width"
405 }, function( prefix, suffix ) {
406 jQuery.cssHooks[ prefix + suffix ] = {
407 expand: function( value ) {
408 var i = 0,
409 expanded = {},
411 // Assumes a single number if not a string
412 parts = typeof value === "string" ? value.split( " " ) : [ value ];
414 for ( ; i < 4; i++ ) {
415 expanded[ prefix + cssExpand[ i ] + suffix ] =
416 parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
419 return expanded;
423 if ( prefix !== "margin" ) {
424 jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
426 } );
428 jQuery.fn.extend( {
429 css: function( name, value ) {
430 return access( this, function( elem, name, value ) {
431 var styles, len,
432 map = {},
433 i = 0;
435 if ( Array.isArray( name ) ) {
436 styles = getStyles( elem );
437 len = name.length;
439 for ( ; i < len; i++ ) {
440 map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
443 return map;
446 return value !== undefined ?
447 jQuery.style( elem, name, value ) :
448 jQuery.css( elem, name );
449 }, name, value, arguments.length > 1 );
451 } );
453 return jQuery;
454 } );