Fix #13543. offsetWidth is wrong on non-1 zoom. Close gh-1194.
[jquery.git] / src / css.js
blob522982b3f772d775df1c72e5032bca2d3225a933
1 var curCSS, iframe,
2         // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
3         // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
4         rdisplayswap = /^(none|table(?!-c[ea]).+)/,
5         rmargin = /^margin/,
6         rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
7         rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
8         rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
9         elemdisplay = { BODY: "block" },
11         cssShow = { position: "absolute", visibility: "hidden", display: "block" },
12         cssNormalTransform = {
13                 letterSpacing: 0,
14                 fontWeight: 400
15         },
17         cssExpand = [ "Top", "Right", "Bottom", "Left" ],
18         cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
20 // return a css property mapped to a potentially vendor prefixed property
21 function vendorPropName( style, name ) {
23         // shortcut for names that are not vendor prefixed
24         if ( name in style ) {
25                 return name;
26         }
28         // check for vendor prefixed names
29         var capName = name.charAt(0).toUpperCase() + name.slice(1),
30                 origName = name,
31                 i = cssPrefixes.length;
33         while ( i-- ) {
34                 name = cssPrefixes[ i ] + capName;
35                 if ( name in style ) {
36                         return name;
37                 }
38         }
40         return origName;
43 function isHidden( elem, el ) {
44         // isHidden might be called from jQuery#filter function;
45         // in that case, element will be second argument
46         elem = el || elem;
47         return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
50 // NOTE: we've included the "window" in window.getComputedStyle
51 // because jsdom on node.js will break without it.
52 function getStyles( elem ) {
53         return window.getComputedStyle( elem, null );
56 function showHide( elements, show ) {
57         var display, elem, hidden,
58                 values = [],
59                 index = 0,
60                 length = elements.length;
62         for ( ; index < length; index++ ) {
63                 elem = elements[ index ];
64                 if ( !elem.style ) {
65                         continue;
66                 }
68                 values[ index ] = jQuery._data( elem, "olddisplay" );
69                 display = elem.style.display;
70                 if ( show ) {
71                         // Reset the inline display of this element to learn if it is
72                         // being hidden by cascaded rules or not
73                         if ( !values[ index ] && display === "none" ) {
74                                 elem.style.display = "";
75                         }
77                         // Set elements which have been overridden with display: none
78                         // in a stylesheet to whatever the default browser style is
79                         // for such an element
80                         if ( elem.style.display === "" && isHidden( elem ) ) {
81                                 values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
82                         }
83                 } else {
85                         if ( !values[ index ] ) {
86                                 hidden = isHidden( elem );
88                                 if ( display && display !== "none" || !hidden ) {
89                                         jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
90                                 }
91                         }
92                 }
93         }
95         // Set the display of most of the elements in a second loop
96         // to avoid the constant reflow
97         for ( index = 0; index < length; index++ ) {
98                 elem = elements[ index ];
99                 if ( !elem.style ) {
100                         continue;
101                 }
102                 if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
103                         elem.style.display = show ? values[ index ] || "" : "none";
104                 }
105         }
107         return elements;
110 jQuery.fn.extend({
111         css: function( name, value ) {
112                 return jQuery.access( this, function( elem, name, value ) {
113                         var styles, len,
114                                 map = {},
115                                 i = 0;
117                         if ( jQuery.isArray( name ) ) {
118                                 styles = getStyles( elem );
119                                 len = name.length;
121                                 for ( ; i < len; i++ ) {
122                                         map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
123                                 }
125                                 return map;
126                         }
128                         return value !== undefined ?
129                                 jQuery.style( elem, name, value ) :
130                                 jQuery.css( elem, name );
131                 }, name, value, arguments.length > 1 );
132         },
133         show: function() {
134                 return showHide( this, true );
135         },
136         hide: function() {
137                 return showHide( this );
138         },
139         toggle: function( state ) {
140                 var bool = typeof state === "boolean";
142                 return this.each(function() {
143                         if ( bool ? state : isHidden( this ) ) {
144                                 jQuery( this ).show();
145                         } else {
146                                 jQuery( this ).hide();
147                         }
148                 });
149         }
152 jQuery.extend({
153         // Add in style property hooks for overriding the default
154         // behavior of getting and setting a style property
155         cssHooks: {
156                 opacity: {
157                         get: function( elem, computed ) {
158                                 if ( computed ) {
159                                         // We should always get a number back from opacity
160                                         var ret = curCSS( elem, "opacity" );
161                                         return ret === "" ? "1" : ret;
162                                 }
163                         }
164                 }
165         },
167         // Exclude the following css properties to add px
168         cssNumber: {
169                 "columnCount": true,
170                 "fillOpacity": true,
171                 "fontWeight": true,
172                 "lineHeight": true,
173                 "opacity": true,
174                 "orphans": true,
175                 "widows": true,
176                 "zIndex": true,
177                 "zoom": true
178         },
180         // Add in properties whose names you wish to fix before
181         // setting or getting the value
182         cssProps: {
183                 // normalize float css property
184                 "float": "cssFloat"
185         },
187         // Get and set the style property on a DOM Node
188         style: function( elem, name, value, extra ) {
189                 // Don't set styles on text and comment nodes
190                 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
191                         return;
192                 }
194                 // Make sure that we're working with the right name
195                 var ret, type, hooks,
196                         origName = jQuery.camelCase( name ),
197                         style = elem.style;
199                 name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
201                 // gets hook for the prefixed version
202                 // followed by the unprefixed version
203                 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
205                 // Check if we're setting a value
206                 if ( value !== undefined ) {
207                         type = typeof value;
209                         // convert relative number strings (+= or -=) to relative numbers. #7345
210                         if ( type === "string" && (ret = rrelNum.exec( value )) ) {
211                                 value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
212                                 // Fixes bug #9237
213                                 type = "number";
214                         }
216                         // Make sure that NaN and null values aren't set. See: #7116
217                         if ( value == null || type === "number" && isNaN( value ) ) {
218                                 return;
219                         }
221                         // If a number was passed in, add 'px' to the (except for certain CSS properties)
222                         if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
223                                 value += "px";
224                         }
226                         // Fixes #8908, it can be done more correctly by specifying setters in cssHooks,
227                         // but it would mean to define eight (for every problematic property) identical functions
228                         if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
229                                 style[ name ] = "inherit";
230                         }
232                         // If a hook was provided, use that value, otherwise just set the specified value
233                         if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
234                                 style[ name ] = value;
235                         }
237                 } else {
238                         // If a hook was provided get the non-computed value from there
239                         if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
240                                 return ret;
241                         }
243                         // Otherwise just get the value from the style object
244                         return style[ name ];
245                 }
246         },
248         css: function( elem, name, extra, styles ) {
249                 var val, num, hooks,
250                         origName = jQuery.camelCase( name );
252                 // Make sure that we're working with the right name
253                 name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
255                 // gets hook for the prefixed version
256                 // followed by the unprefixed version
257                 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
259                 // If a hook was provided get the computed value from there
260                 if ( hooks && "get" in hooks ) {
261                         val = hooks.get( elem, true, extra );
262                 }
264                 // Otherwise, if a way to get the computed value exists, use that
265                 if ( val === undefined ) {
266                         val = curCSS( elem, name, styles );
267                 }
269                 //convert "normal" to computed value
270                 if ( val === "normal" && name in cssNormalTransform ) {
271                         val = cssNormalTransform[ name ];
272                 }
274                 // Return, converting to number if forced or a qualifier was provided and val looks numeric
275                 if ( extra === "" || extra ) {
276                         num = parseFloat( val );
277                         return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
278                 }
279                 return val;
280         }
283 curCSS = function( elem, name, _computed ) {
284         var width, minWidth, maxWidth,
285                 computed = _computed || getStyles( elem ),
287                 // Support: IE9
288                 // getPropertyValue is only needed for .css('filter') in IE9, see #12537
289                 ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
290                 style = elem.style;
292         if ( computed ) {
294                 if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
295                         ret = jQuery.style( elem, name );
296                 }
298                 // Support: Chrome <17, Safari 5.1
299                 // A tribute to the "awesome hack by Dean Edwards"
300                 // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
301                 // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
302                 // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
303                 if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
305                         // Remember the original values
306                         width = style.width;
307                         minWidth = style.minWidth;
308                         maxWidth = style.maxWidth;
310                         // Put in the new values to get a computed value out
311                         style.minWidth = style.maxWidth = style.width = ret;
312                         ret = computed.width;
314                         // Revert the changed values
315                         style.width = width;
316                         style.minWidth = minWidth;
317                         style.maxWidth = maxWidth;
318                 }
319         }
321         return ret;
325 function setPositiveNumber( elem, value, subtract ) {
326         var matches = rnumsplit.exec( value );
327         return matches ?
328                 // Guard against undefined "subtract", e.g., when used as in cssHooks
329                 Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
330                 value;
333 function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
334         var i = extra === ( isBorderBox ? "border" : "content" ) ?
335                 // If we already have the right measurement, avoid augmentation
336                 4 :
337                 // Otherwise initialize for horizontal or vertical properties
338                 name === "width" ? 1 : 0,
340                 val = 0;
342         for ( ; i < 4; i += 2 ) {
343                 // both box models exclude margin, so add it if we want it
344                 if ( extra === "margin" ) {
345                         val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
346                 }
348                 if ( isBorderBox ) {
349                         // border-box includes padding, so remove it if we want content
350                         if ( extra === "content" ) {
351                                 val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
352                         }
354                         // at this point, extra isn't border nor margin, so remove border
355                         if ( extra !== "margin" ) {
356                                 val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
357                         }
358                 } else {
359                         // at this point, extra isn't content, so add padding
360                         val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
362                         // at this point, extra isn't content nor padding, so add border
363                         if ( extra !== "padding" ) {
364                                 val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
365                         }
366                 }
367         }
369         return val;
372 function getWidthOrHeight( elem, name, extra ) {
374         // Start with offset property, which is equivalent to the border-box value
375         var valueIsBorderBox = true,
376                 val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
377                 styles = getStyles( elem ),
378                 isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
380         // some non-html elements return undefined for offsetWidth, so check for null/undefined
381         // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
382         // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
383         if ( val <= 0 || val == null ) {
384                 // Fall back to computed then uncomputed css if necessary
385                 val = curCSS( elem, name, styles );
386                 if ( val < 0 || val == null ) {
387                         val = elem.style[ name ];
388                 }
390                 // Computed unit is not pixels. Stop here and return.
391                 if ( rnumnonpx.test(val) ) {
392                         return val;
393                 }
395                 // we need the check for style in case a browser which returns unreliable values
396                 // for getComputedStyle silently falls back to the reliable elem.style
397                 valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
399                 // Normalize "", auto, and prepare for extra
400                 val = parseFloat( val ) || 0;
401         }
403         // use the active box-sizing model to add/subtract irrelevant styles
404         return ( val +
405                 augmentWidthOrHeight(
406                         elem,
407                         name,
408                         extra || ( isBorderBox ? "border" : "content" ),
409                         valueIsBorderBox,
410                         styles
411                 )
412         ) + "px";
415 // Try to determine the default display value of an element
416 function css_defaultDisplay( nodeName ) {
417         var doc = document,
418                 display = elemdisplay[ nodeName ];
420         if ( !display ) {
421                 display = actualDisplay( nodeName, doc );
423                 // If the simple way fails, read from inside an iframe
424                 if ( display === "none" || !display ) {
425                         // Use the already-created iframe if possible
426                         iframe = ( iframe ||
427                                 jQuery("<iframe frameborder='0' width='0' height='0'/>")
428                                 .css( "cssText", "display:block !important" )
429                         ).appendTo( doc.documentElement );
431                         // Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
432                         doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
433                         doc.write("<!doctype html><html><body>");
434                         doc.close();
436                         display = actualDisplay( nodeName, doc );
437                         iframe.detach();
438                 }
440                 // Store the correct default display
441                 elemdisplay[ nodeName ] = display;
442         }
444         return display;
447 // Called ONLY from within css_defaultDisplay
448 function actualDisplay( name, doc ) {
449         var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
450                 display = jQuery.css( elem[0], "display" );
451         elem.remove();
452         return display;
455 jQuery.each([ "height", "width" ], function( i, name ) {
456         jQuery.cssHooks[ name ] = {
457                 get: function( elem, computed, extra ) {
458                         if ( computed ) {
459                                 // certain elements can have dimension info if we invisibly show them
460                                 // however, it must have a current display style that would benefit from this
461                                 return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
462                                         jQuery.swap( elem, cssShow, function() {
463                                                 return getWidthOrHeight( elem, name, extra );
464                                         }) :
465                                         getWidthOrHeight( elem, name, extra );
466                         }
467                 },
469                 set: function( elem, value, extra ) {
470                         var styles = extra && getStyles( elem );
471                         return setPositiveNumber( elem, value, extra ?
472                                 augmentWidthOrHeight(
473                                         elem,
474                                         name,
475                                         extra,
476                                         jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
477                                         styles
478                                 ) : 0
479                         );
480                 }
481         };
484 // These hooks cannot be added until DOM ready because the support test
485 // for it is not run until after DOM ready
486 jQuery(function() {
487         if ( !jQuery.support.reliableMarginRight ) {
488                 jQuery.cssHooks.marginRight = {
489                         get: function( elem, computed ) {
490                                 if ( computed ) {
491                                         // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
492                                         // Work around by temporarily setting element display to inline-block
493                                         return jQuery.swap( elem, { "display": "inline-block" },
494                                                 curCSS, [ elem, "marginRight" ] );
495                                 }
496                         }
497                 };
498         }
500         // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
501         // getComputedStyle returns percent when specified for top/left/bottom/right
502         // rather than make the css module depend on the offset module, we just check for it here
503         if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
504                 jQuery.each( [ "top", "left" ], function( i, prop ) {
505                         jQuery.cssHooks[ prop ] = {
506                                 get: function( elem, computed ) {
507                                         if ( computed ) {
508                                                 computed = curCSS( elem, prop );
509                                                 // if curCSS returns percentage, fallback to offset
510                                                 return rnumnonpx.test( computed ) ?
511                                                         jQuery( elem ).position()[ prop ] + "px" :
512                                                         computed;
513                                         }
514                                 }
515                         };
516                 });
517         }
521 if ( jQuery.expr && jQuery.expr.filters ) {
522         jQuery.expr.filters.hidden = function( elem ) {
523                 // Support: Opera <= 12.12
524                 // Opera reports offsetWidths and offsetHeights less than zero on some elements
525                 return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;
526         };
528         jQuery.expr.filters.visible = function( elem ) {
529                 return !jQuery.expr.filters.hidden( elem );
530         };
533 // These hooks are used by animate to expand properties
534 jQuery.each({
535         margin: "",
536         padding: "",
537         border: "Width"
538 }, function( prefix, suffix ) {
539         jQuery.cssHooks[ prefix + suffix ] = {
540                 expand: function( value ) {
541                         var i = 0,
542                                 expanded = {},
544                                 // assumes a single number if not a string
545                                 parts = typeof value === "string" ? value.split(" ") : [ value ];
547                         for ( ; i < 4; i++ ) {
548                                 expanded[ prefix + cssExpand[ i ] + suffix ] =
549                                         parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
550                         }
552                         return expanded;
553                 }
554         };
556         if ( !rmargin.test( prefix ) ) {
557                 jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
558         }