jQuery源码解析之position()
position()
作用:
返回被选元素相对于父元素(parent)的偏移坐标
使用:
直接调用$().position()
即可,该方法没有 arguments(参数对象)
<body> <script src="jQuery.js"></script> <p id="pTwo">这是divTwo</p> <script> $("#pTwo").position() //{top: 0, left: 8} </script> </body>
源码:
// 返回被选元素相对于父元素(parent)的偏移坐标 // 可以理解成被选元素设置为absolute, // 然后设置left、top的值就是相对于父元素的偏移坐标 // 源码10571行 // position() relates an element's margin box to its offset parent's padding box // This corresponds to the behavior of CSS absolute positioning position: function() { // 如果DOM元素不存在,直接返回 if ( !this[ 0 ] ) { return; } var offsetParent, offset, doc, elem = this[ 0 ], parentOffset = { top: 0, left: 0 }; // position:fixed elements are offset from the viewport, which itself always has zero offset // position:fixed的元素,是相对于浏览器窗口进行定位的, // 所以它的偏移就是getBoundingClientRect(),即获取某个元素相对于视窗的位置 if ( jQuery.css( elem, "position" ) === "fixed" ) { // Assume position:fixed implies availability of getBoundingClientRect offset = elem.getBoundingClientRect(); } // 除去position是fixed的情况 else { // 获取被选元素相对于文档(document)的偏移坐标 offset = this.offset(); // Account for the *real* offset parent, which can be the document or its root element // when a statically positioned element is identified doc = elem.ownerDocument; //定位目标元素的父元素(position不为static的元素) offsetParent = elem.offsetParent || doc.documentElement; // 如果父元素是<body>/<html>的话,将父元素重新定位为它们的父元素 // body的父元素是html,html的父元素是document while ( offsetParent && ( offsetParent === doc.body || offsetParent === doc.documentElement ) && jQuery.css( offsetParent, "position" ) === "static" ) { offsetParent = offsetParent.parentNode; } // 如果定位父元素存在,并且不等于目标元素,并且定位元素类型是 "元素类型" if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) { // Incorporate borders into its offset, since they are outside its content origin parentOffset = jQuery( offsetParent ).offset(); // 这两行代码的意思是父元素的offset()要从padding算起,不包括border // 所以需要去掉border // jQuery.css( element, "borderTopWidth", true )的 true 表示返回数字,而不带单位 px parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true ); parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true ); } } // Subtract parent offsets and element margins // 可以看出,$().position()的本质是目标元素的offset()减去父元素的offset(),同时还要算上目标元素的margin,因为盒子模型(关键)。 //(注意:offset()的本质是getBoundingClientRect()的top、left + pageYOffset、pageXOffset) return { top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true ) }; },
解析:
整体上看,是一个 if(...fixed) { } esle { }
语句
(1)if ( jQuery.css( elem, "position" ) === "fixed" )
if ( jQuery.css( elem, "position" ) === "fixed" ) { // Assume position:fixed implies availability of getBoundingClientRect offset = elem.getBoundingClientRect(); } return { top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true ) };
由于position:fixed
的元素,是相对于浏览器窗口进行定位的,所以它的偏移就是getBoundingClientRect()
,即获取某个元素相对于视窗的位置。
注意:
① getBoundingClientRect() 计算的是目标元素的border的位置(左上角),是不包括margin的
② 如果不加上margin的话(代码是通过减去
,来算上margin的),是不准确的,看下图
所以源码最后会:
- jQuery.css( elem, "marginTop", true ) - jQuery.css( elem, "marginLeft", true )
(2)jQuery.css( elem, "width", true )
true
的作用是返回该属性的数字,而不带单位 px
(3)定位父元素存在,并且不等于目标元素,并且定位元素类型是 "元素类型"的话
if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 )
是要减去border
属性的值的
parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true ); parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
为啥?举个例子:
let p=document.querySelector("#pTwo") console.log(p.getBoundingClientRect(),'pTwo11'); //x:8,y:16
设置 borderLeftWidth 为 8 像素
let p=document.querySelector("#pTwo") p.style.borderLeftWidth='8px' console.log(p.getBoundingClientRect(),'pTwo11'); //x:8,y:16
可以看到getBoundingClientRect()
指定坐标是到border
上的,这是不准确的,因为在里面的子元素的位置也会受父元素的border
影响,所以父元素的坐标需要越过border
综上:
可以看出,$().position()
的本质是目标元素的 offset() 减去父元素的 offset(),同时还要算上目标元素的 margin,算上父元素的border。
(注意:offset()的本质是getBoundingClientRect()的top、left + pageYOffset、pageXOffset)
Github:
https://github.com/AttackXiao...
(完)