一、在讲之前,先弄清 boxSizing 属性
(1)box-sizing 是默认值 "content-box"复制代码
$().width()
的值是 55
(2)box-sizing 是 "border-box"
这是divTwo 复制代码
$().width()
的值是 53
因为 border-box 是包括 border、padding、content 的,而 content-box 只包括 content。
可想而知,jQuery的$()
.width() 中也包含了对 borderBox 的判断。 - 注意下
div
标签的默认值
二、$()
.width()
源码:
//源码7033行 //$.each(obj,callback(index,item){}) jQuery.each( [ "height", "width" ], function( i, dimension ) { //i:0 dimension:height //i:1 dimension:width //cssHooks是用来定义style方法的 jQuery.cssHooks[ dimension ] = { //读 //$().width() //参数:elem:目标DOM元素/computed:true/extra:"content" get: function( elem, computed, extra ) { console.log(elem, computed, extra,'extra7040') if ( computed ) { // 某些元素是有尺寸的信息的,如果我们隐式地显示它们,前提是它必须有一个display值 // Certain elements can have dimension info if we invisibly show them // but it must have a current display style that would benefit // 上面这句话的意思是,某个元素用display:none,将它从页面上去掉了,此时是获取不到它的宽度的 // 如果要获取它的宽度的话,需要隐式地显示它们,比如display:absolute,visible:hidden // 然后再去获取它的宽度 // block:false // none:true // rdisplayswap的作用是检测 none和table开头的 return rdisplayswap.test( jQuery.css( elem, "display" ) ) && // 兼容性的考虑,直接看 getWidthOrHeight // Support: Safari 8+ // Table columns in Safari have non-zero offsetWidth & zero // getBoundingClientRect().width unless display is changed. // Support: IE <=11 only // Running getBoundingClientRect on a disconnected node // in IE throws an error. // display为none的话,elem.getBoundingClientRect().width=0 // elem.getClientRects() 返回CSS边框的集合 // https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getClientRects ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? swap( elem, cssShow, function() { return getWidthOrHeight( elem, dimension, extra ); } ) : //$().width()情况 //dimension:width/extra:"content" getWidthOrHeight( elem, dimension, extra ); } }, }; } ); 复制代码
解析:
(1)box-sizing 是默认值,并且 display 不为 none①rdisplayswap
作用:检测目标元素的display
属性的值 是否为none
或以table
开头 // 检测 display 的值是否为 none 或以 table 开头 // Swappable if display is none or starts with table // 除了 "table", "table-cell", "table-caption" // except "table", "table-cell", or "table-caption" // display 的值,请访问 https://developer.mozilla.org/en-US/docs/CSS/display // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display // 源码6698行 var rdisplayswap = /^(none|table(?!-c[ea]).+)/, 复制代码
如果display
是none
的话,就会调用swap()
方法,反之,就直接调用getWidthOrHeight()
方法
② getWidthOrHeight()
width
或height
的值 //获取 width 或 height //dimension:width/extra:"content" //源码6823行 function getWidthOrHeight( elem, dimension, extra ) { // Start with computed style var styles = getStyles( elem ), val = curCSS( elem, dimension, styles ), //判断 box-sizing 的值是否 是 border-box //如果启用了 box-sizing,js 的 width 是会算上 margin、border、padding的 //如果不启用的话,js 的 width 只会算 content //jQuery 的 width 自始至终都是算的 content isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", valueIsBorderBox = isBorderBox; //火狐兼容性处理,可不看 // Support: Firefox <=54 // Return a confounding non-pixel value or feign ignorance, as appropriate. if ( rnumnonpx.test( val ) ) { if ( !extra ) { return val; } val = "auto"; } // 通过getComputedStyle检查style属性,并返回可靠的style属性,这样可以防止浏览器返回不可靠的值 // Check for style in case a browser which returns unreliable values // for getComputedStyle silently falls back to the reliable elem.style valueIsBorderBox = valueIsBorderBox && ( support.boxSizingReliable() || val === elem.style[ dimension ] ); console.log(valueIsBorderBox,'valueIsBorderBox6853') // Fall back to offsetWidth/offsetHeight when value is "auto" // This happens for inline elements with no explicit setting (gh-3571) // Support: Android <=4.1 - 4.3 only // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) if ( val === "auto" || !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) { val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ]; console.log(val,'val6862') // offsetWidth/offsetHeight provide border-box values valueIsBorderBox = true; } // Normalize "" and auto // 55px val = parseFloat( val ) || 0; console.log(val,extra,'val6869') // Adjust for the element's box model return ( val + boxModelAdjustment( //DOM节点 elem, //width dimension, //content extra || ( isBorderBox ? "border" : "content" ), //true/false valueIsBorderBox, //styles styles, //55 // Provide the current computed size to request scroll gutter calculation (gh-3589) val ) ) + "px"; } 复制代码
getWidthOrHeight() 里面有好多方法,我们一一来解析:
③ getStyles( elem )
//获取该DOM元素的所有css属性的值 //源码6501行 var getStyles = function( elem ) { // 兼容性处理,旨在拿到正确的view // Support: IE <=11 only, Firefox <=30 (#15098, #14150) // IE throws on elements created in popups // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" var view = elem.ownerDocument.defaultView; if ( !view || !view.opener ) { view = window; } //获取所有CSS属性的值 return view.getComputedStyle( elem ); }; 复制代码
可以看到,本质是调用了getComputedStyle()
方法。
④ curCSS( elem, dimension, styles )
// 获取元素的当前属性的值 // elem, "position" // elem,width,styles // 源码6609行 function curCSS( elem, name, computed ) { var width, minWidth, maxWidth, ret, // Support: Firefox 51+ // Retrieving style before computed somehow // fixes an issue with getting wrong values // on detached elements style = elem.style; //获取elem所有的样式属性 computed = computed || getStyles( elem ); // console.log(computed,'computed6621') // getPropertyValue is needed for: // .css('filter') (IE 9 only, #12537) // .css('--customProperty) (#3144) if ( computed ) { //返回元素的属性的当前值 //position:static //top:0px //left:0px ret = computed.getPropertyValue( name ) || computed[ name ]; console.log(ret,'ret6627') //如果目标属性值为空并且目标元素不在目标元素所在的文档内(感觉这种情况好奇怪) if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { //使用jQuery.style方法来获取目标元素的属性值 ret = jQuery.style( elem, name ); } // A tribute to the "awesome hack by Dean Edwards" // Android Browser returns percentage for some values, // but width seems to be reliably pixels. // This is against the CSSOM draft spec: // https://drafts.csswg.org/cssom/#resolved-values //当属性设置成数值时,安卓浏览器会返回一些百分比,但是宽度是像素显示的 //这违反了CSSOM草案规范 //所以以下方法是修复不规范的width属性的 if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { // Remember the original values width = style.width; minWidth = style.minWidth; maxWidth = style.maxWidth; // Put in the new values to get a computed value out style.minWidth = style.maxWidth = style.width = ret; ret = computed.width; // Revert the changed values style.width = width; style.minWidth = minWidth; style.maxWidth = maxWidth; } } return ret !== undefined ? // 兼容性,IE下返回的zIndex的值是数字, // 而使用jQuery获取的属性都是返回字符串 // Support: IE <=9 - 11 only // IE returns zIndex value as an integer. ret + "" : ret; } 复制代码
可以看到,curCSS
本质是调用了computed.getPropertyValue( name )
方法,也就是说我们可以这样去获取目标元素的属性值:
let a=document.getElementById("pTwo") a.ownerDocument.defaultView.getComputedStyle(a).getPropertyValue('width') //55px 复制代码
目标元素的所属 view,调用getComputedStyle()
方法,获取目标元素的所有 CSS 属性,再调用getPropertyValue('width')
,获取目标width
的属性值,为 55px
注意:无论box-sizing
的值是border-box
还是content-box
,上面的方法获取的width
值都是55px
,这是不符合 CSS3 盒子模型的,所以 jQuery 拿到该值后,还会继续处理。
⑤ boxModelAdjustment
boxModelAdjustment()
会直接返回 0 综上:当box-sizing 是默认值,并且 display 不为 none
时,返回的width
是:
parseFloat(a.ownerDocument.defaultView.getComputedStyle(a).getPropertyValue('width')) //55 复制代码
(2)box-sizing 值为 border-box
这是divTwo 复制代码
$("#pTwo").width() //51 document.getElementById("pTwo").style.width //55px 复制代码
可以看到,原生 js 获取 width 是不遵循 CSS3 盒子规范的。
borderBox 的判断在getWidthOrHeight()
方法中,直接看过去:
//获取 width 或 height //dimension:width/extra:"content" //源码6823行 function getWidthOrHeight( elem, dimension, extra ) { xxx ... var styles = getStyles( elem ), //true isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", //true valueIsBorderBox = isBorderBox; xxx ... valueIsBorderBox = valueIsBorderBox && //val值是通过a.ownerDocument.defaultView.getComputedStyle(a).getPropertyValue('width')得出的 //但又通过js原生的style.width来取值并与val相比较 ( support.boxSizingReliable() || val === elem.style[ dimension ] ); console.log(val === elem.style[ dimension ],'valueIsBorderBox6853') // 55 val = parseFloat( val ) || 0; // Adjust for the element's box model return ( val + //borderBox走这里 boxModelAdjustment( //DOM节点 elem, //width dimension, //content extra || ( isBorderBox ? "border" : "content" ), //true/false valueIsBorderBox, //styles styles, //55 // Provide the current computed size to request scroll gutter calculation (gh-3589) val ) ) + "px"; } 复制代码
boxModelAdjustment():
作用:集中处理borderBox的情况//参数说明: //elem:DOM节点/dimension:width/box:content/isBorderBox:true/false/styles:styles/computedVal:55 //源码6758行 function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { var i = dimension === "width" ? 1 : 0, extra = 0, delta = 0; // 如果 boxSizing 的属性值,而不是 borderBox 的话,就直接返回 0 // Adjustment may not be necessary if ( box === ( isBorderBox ? "border" : "content" ) ) { console.log('content1111','content6768') return 0; } //小技巧 //i 的初始值是 0/1 //然后 cssExpand = [ "Top", "Right", "Bottom", "Left" ] for ( ; i < 4; i += 2 ) { // Both box models exclude margin if ( box === "margin" ) { //var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; //width 的话,就是 marginRight/marginLeft //height 的话,就是 marginTop/marginBottom //jQuery.css( elem, box + cssExpand[ i ], true, styles ) 的意思就是 //返回 marginRight/marginLeft/marginTop/marginBottom 的数字,并给 delta 加上 delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } // If we get here with a content-box, we're seeking "padding" or "border" or "margin" // 如果不是 borderBox 的话 if ( !isBorderBox ) { // Add padding // 添加 padding-xxx delta += 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 ); } // If we get here with a border-box (content + padding + border), we're seeking "content" or // "padding" or "margin" } else { // 去掉 padding // For "content", subtract padding if ( box === "content" ) { //width,去掉paddingLeft,paddingRight的值 delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } // For "content" or "padding", subtract border // 去掉 borderXXXWidth if ( box !== "margin" ) { //width,去掉borderLeftWidth,borderRightWidth的值 delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } } // 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( //就是将dimension的首字母做个大写 elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - computedVal - delta - extra - 0.5 ) ); } return delta; } 复制代码
可以看到,isBorderBox 为 true 的话,会执行下面两段代码:
if ( box === "content" ) { //width,去掉paddingLeft,paddingRight的值 delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } 复制代码
if ( box !== "margin" ) { //width,去掉borderLeftWidth,borderRightWidth的值 delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } 复制代码
去除了paddingLeft
、paddingRight
、borderLeftWidth
和borderRightWidth
,并最终返回值
二、$()
.width(xxx)
源码:
//源码7033行 //$.each(obj,callback(index,item){}) jQuery.each( [ "height", "width" ], function( i, dimension ) { //i:0 dimension:height //i:1 dimension:width //cssHooks是用来定义style方法的 jQuery.cssHooks[ dimension ] = { //写 //$().width(55) //elem:DOM节点,value:55,extra:content set: function( elem, value, extra ) { var matches, styles = getStyles( elem ), isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", //-4 subtract = extra && boxModelAdjustment( elem, dimension, extra, isBorderBox, styles ); // 如果是 borderBox 的话,通过 offset 计算的尺寸是不准的, // 所以要假设成 content-box 来获取 border 和 padding // Account for unreliable border-box dimensions by comparing offset* to computed and // faking a content-box to get border and padding (gh-3699) //true true 'static' //调整 subtract if ( isBorderBox && support.scrollboxSize() === styles.position ) { subtract -= Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - parseFloat( styles[ dimension ] ) - boxModelAdjustment( elem, dimension, "border", false, styles ) - 0.5 ); console.log(subtract,'subtract7169') } // 如果需要进行值调整,则转换为像素 // Convert to pixels if value adjustment is needed //如果是 borderBox 并且 value 的单位不是 px,则会转换成像素 if ( subtract && ( matches = rcssNum.exec( value ) ) && ( matches[ 3 ] || "px" ) !== "px" ) { elem.style[ dimension ] = value; value = jQuery.css( elem, dimension ); } //59px return setPositiveNumber( elem, value, subtract ); } }; } ); 复制代码
解析:
(1)整体上看,实际上两个 if ,最后再 return 一个setPositiveNumber()
方法 (2)注意subtract
,如果有 borderBox 属性,并且 borderWidth、padding 有值的话,subtract 一般为负数,比如下面的例子,subtract = -4
这是divTwo $("#pTwo").width(55) 复制代码
反之则会是 0
(3)两个 if 我试了下,都会去执行,所以直接看的setPositiveNumber ()
setPositiveNumber:
作用:设置真正的 width 值function setPositiveNumber( elem, value, subtract ) { // 标准化相对值 // Any relative (+/-) values have already been // normalized at this point //[ // "55px", // undefined, // "55", // "px", // index: 0, // input: "55px", // groups: undefined, // index: 0 // input: "55px" // ] var matches = rcssNum.exec( value ); console.log(matches,( subtract || 0 ),'matches6760') return matches ? //(0,55-(-4))+'px' //Math.max(a,b) 返回两个指定的数中带有较大的值的那个数 // Guard against undefined "subtract", e.g., when used as in cssHooks Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : value; } 复制代码
如果是 borderBox,width 会设置成 59px(虽然表面上开发者设置的是$("#pTwo").width(55)
),反之,则是 55px
总结:
1、$()
.width()(1)不是borderBox
$().width()=parseFloat(elem.ownerDocument.defaultView.getComputedStyle(elem).getPropertyValue('width'))
(2)是borderBox()
boxModelAdjustment()
方法,去除 borderWidth、padding 2、$()
.width(xxx)
borderBox
width=xxx(2)是borderBox
width=xxx+ setPositiveNumber() (完)