一、在讲之前,先弄清 boxSizing 属性
(1)box-sizing 是默认值 "content-box"
这是divTwo
$().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 属性的值
//获取该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
$("#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()
在(1)的基础上执行boxModelAdjustment()
方法,去除 borderWidth、padding
2、$()
.width(xxx)
(1)不是borderBox
width=xxx
(2)是borderBox
width=xxx+ setPositiveNumber()
(完)