乐趣区

jQuery源码解析之offset

一、offset()

作用:
返回被选元素相对于 文档(document)的偏移坐标

二、三种情况使用:

1、$().offset()

<body>
<script src="jQuery.js"></script>
<p id="pTwo"> 这是 divTwo</p>
<script>
  $("#pTwo").offset() //{top: 16, left: 8}
</script>
</body>

源码:

   // 返回目标元素相对于 doucument 的偏移坐标,// 即距离浏览器左上角的距离

    // 返回偏移坐标:$(selector).offset()
    // 设置偏移坐标:$(selector).offset({top:value,left:value})
    // 使用函数设置偏移坐标:$(selector).offset(function(index,currentoffset))
    // offset() relates an element's border box to the document origin
    // 源码 10500 行
    //options 即参数
    //arguments 是参数对象
    offset: function(options) {
      // Preserve chaining for setter
      // 如果是有参数的,参数是 undefined 则返回目标元素本身,// 否则为每个目标元素设置 options
      console.log(options,arguments,'arguments10476')
      //$().offset()不走这里
      if (arguments.length) {console.log('aaa','vvv10507')
        return options === undefined ?
          this :
          this.each(function( i) {
            // 为每个目标元素设置 options
            jQuery.offset.setOffset(this, options, i);
          } );
      }

      var rect, win,
        // 获取 DOM 节点
        elem = this[0];

      if (!elem) {return;}

      // jQuery 不支持获取隐藏元素的偏移坐标。// 同理,也无法取得隐藏元素的 border, margin, 或 padding 信息
      // 所以如果元素是隐藏的,默认返回 0 值
      // Return zeros for disconnected and hidden (display: none) elements (gh-2310)
      // Support: IE <=11 only
      // Running getBoundingClientRect on a
      // disconnected node in IE throws an error
      // 对 IE 的特殊处理
      if (!elem.getClientRects().length ) {return { top: 0, left: 0};
      }

      // Get document-relative position by adding viewport scroll to viewport-relative gBCR
      // 返回元素的大小及其相对于视口的位置
      //https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect
      rect = elem.getBoundingClientRect();
      // 返回所属文档的默认窗口对象(只读)// 原点坐标
      win = elem.ownerDocument.defaultView;
      //pageXOffset,pageYOffset 相当于 scrollX 和 scrollY 
      // 返回文档在窗口左上角水平 x 和垂直 y 方向滚动的像素
      return {
        //16    0
        //
        top: rect.top + win.pageYOffset,
        //8     0
        left: rect.left + win.pageXOffset
      };
    },

解析:
由于 $().offset() 没有参数,所以源码里的两个 if 可以忽略,所以 offset() 的本质即:

  let p = document.getElementById("pTwo");
  let rect=p.getBoundingClientRect()
  // 返回所属文档的默认窗口对象(只读)// 原点坐标
  let win = p.ownerDocument.defaultView;
  let offsetObj={
    top: rect.top + win.pageYOffset,
    left: rect.left + win.pageXOffset
  }
  console.log(offsetObj,'win18')

(1)getBoundingClientRect()
该方法用于获取某个元素相对于视窗的位置集合,并返回一个对象,该对象中有 top, right, bottom, left 等属性,简单点就是相对于原坐标(默认是左上角)的偏移量

(2)window.pageXOffset、window.pageYOffset
返回文档在窗口左上角水平 x 和垂直 y 方向滚动的像素, 相当于 scrollX 和 scrollY,简单点就是滚动的偏移量

所以 offset()本质 即:
相对于原坐标的偏移量 + 滚动的偏移量的总和。

2、$().offset({top:15,left:15})

$("#pTwo").offset({top:15,left:15})

源码:
当有参数的时候,就会走 if 中,通过 jQuery.offset.setOffset() 来处理:

 if (arguments.length) {
        return options === undefined ?
          this :
          this.each(function( i) {
            // 为每个目标元素设置 options
            jQuery.offset.setOffset(this, options, i);
          } );
      }
  • jQuery.offset.setOffset()
  //offset()的关键方法
  // 源码 10403 行
  jQuery.offset = {setOffset: function( elem, options, i) {
      var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
        // 获取元素的 position 属性的值
        //static
        position = jQuery.css(elem, "position"),
        // 过滤成标准 jQuery 对象
        curElem = jQuery(elem),
        props = {};

      // Set position first, in-case top/left are set even on static elem
      // 指定相对定位 relative
      if (position === "static") {elem.style.position = "relative";}
      //{left:8,top:16}
      curOffset = curElem.offset();
      //0px
      curCSSTop = jQuery.css(elem, "top");
      //0px
      curCSSLeft = jQuery.css(elem, "left");
      // 如果定位 position 是(绝对定位 absolute 或固定定位 fixed),// 并且 top,left 属性包含 auto 的话
      //false
      calculatePosition = (position === "absolute" || position === "fixed") &&
        (curCSSTop + curCSSLeft).indexOf("auto") > -1;
      // Need to be able to calculate position if either
      // top or left is auto and position is either absolute or fixed
      if (calculatePosition) {curPosition = curElem.position();
        curTop = curPosition.top;
        curLeft = curPosition.left;

      } else {
        //0 0
        curTop = parseFloat(curCSSTop) || 0;
        curLeft = parseFloat(curCSSLeft) || 0;
      }
      // 如果传的参数是 function{}的话
      if (isFunction( options) ) {// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
        options = options.call(elem, i, jQuery.extend( {}, curOffset ) );
      }
      // 如果传的参数的有 top 值
      if (options.top != null) {// 参数 top - offset().top + element.top
        props.top = (options.top - curOffset.top) + curTop;
      }
      // 如果传的参数的有 left 值
      if (options.left != null) {// 参数 left - offset().left + element.left
        props.left = (options.left - curOffset.left) + curLeft;
      }
      // 自己没见过使用 using 的情况。。if ("using" in options) {options.using.call( elem, props);

      }
      // 所以一般走这里,为当前元素设置 top,left 属性
      else {
        //position:relative
        //top:xxx
        //left:xxx
        curElem.css(props);
      }
    }
  };

解析:
(1)先判断当前元素的 position 的值,没有设置 position 属性的话,默认为 relative,并获取元素的 top、left 属性的值
(2)返回一个对象 obj,obj 的 top 是 参数的 top - 默认偏移(offset)的 top + position 设置的 top(没有设置,默认为 0),obj 的 left 同理。

也就是说 offset({top:15,;eft:15}) 的 本质 为:
参数的属性减去对应的默认 offset 属性加上 position 上的对应属性。

// 伪代码
offset().top = elem. getBoundingClientRect().top + document. pageYOffset

top: offset({top:15}).top - offset().top + position.top
也就是:offset({top:15}).top - (elem. getBoundingClientRect().top + document. pageYOffset) + position.top

3、$().offset(function(){})

  $("#pTwo").offset(function(index,currentoffset){let newPos={};
    newPos.left=currentoffset.left+15;
    newPos.top=currentoffset.top+15;
    return newPos;
  });

源码:

// 如果传的参数是 function{}的话
      if (isFunction( options) ) {// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
        options = options.call(elem, i, jQuery.extend( {}, curOffset ) );
      }

解析:
让当前元素通过 call 去调用参数中的 function(){} 方法,call 的参数必须一个一个放进去,上面源码中,call 参数有 i、jQuery.extend({}, curOffset )

  • jQuery.extend({}, curOffset )

暂不解析jQuery.extend() ,但这里的作用 不用看源码,也知道是将 element.offset() 的属性赋值给新建的空对象 {}。

所以 $().offset(function(){}) 的 本质 即:相对于 element.offset(),对其 top,left 进行操作,而不是像 offset({top:xxx,left:xxx}) 那样相对于左上角原点进行操作(这样就需要先减去 offset()中的 top、left 的值了)。

本文结束,五一愉快~


(完)

退出移动版