jQuery offset()源码解析
首先是原型上的offset方法,根据arguments判断到底是取值还是设值。如果是设置,就遍历调用静态方法jQuery.offset.setOffset
如果是取值。那么就是从"var docElem,win"这里开始了。
jQuery.fn.offset = function( options ) { if ( arguments.length ) {//设置元素坐标 return options === undefined ?//如果传入的参数是undefined,直接返回 this : this.each(function( i ) {//遍历然后调用jQuery.offset.setOffset jQuery.offset.setOffset( this, options, i ); }); } var docElem, win, elem = this[ 0 ], box = { top: 0, left: 0 }, doc = elem && elem.ownerDocument;//当前文档 if ( !doc ) { return; } docElem = doc.documentElement; // Make sure it's not a disconnected DOM node if ( !jQuery.contains( docElem, elem ) ) {//如果元素没有被documentElement包含,就返回 0 0 return box; } // If we don't have gBCR, just use 0,0 rather than error // BlackBerry 5, iOS 3 (original iPhone) if ( typeof elem.getBoundingClientRect !== core_strundefined ) {//如果元素有getBoundingClientRect方法 box = elem.getBoundingClientRect();//调用该方法 } win = getWindow( doc );//如果是window就返回window,如果是document,返回document.defaultView return { //元素相对于视窗的距离+滚动距离-边框宽度 top: box.top + win.pageYOffset - docElem.clientTop, left: box.left + win.pageXOffset - docElem.clientLeft }; };
取值中,它考虑了元素没有被documentElement包含的情况,没有渲染,自然是没有offset的,于是它很神奇的返回了{"top":0,"left":0}。
因为看的是jQuery 2.x的版本,这里它用到了getBoundingClientRect方法,从名字就可以看出,它返回的是相对于视窗的距离,这不是我们需要的offset,jQuery.offset返回的是相对于文档的距离,所以还要做进一步的计算:
return { //元素相对于视窗的距离+滚动距离-边框宽度 top: box.top + win.pageYOffset - docElem.clientTop, left: box.left + win.pageXOffset - docElem.clientLeft };
关于getWindow,看看函数声明:
function getWindow( elem ) { return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView; }
如果elem是window,直接返回window,如果是document,则返回document.defaultView。
不是很理解为什么要使用document.defaultView,群里的小伙伴说可能是多frame的情况
然后我在stackoverflow上找到的帖子 http://stackoverflow.com/questions/9183555/whats-the-point-of-document-defaultview 也有类似的答案,然后另一种可能是为了修复Firefox 3.6中getComputedStyle的bug(⊙o⊙)…
接下来是静态方法设置offset。。。
jQuery.offset = { setOffset: function( elem, options, i ) { var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, position = jQuery.css( elem, "position" ),//获取当前元素的position属性 curElem = jQuery( elem ),//缓存当前元素 props = {}; // Set position first, in-case top/left are set even on static elem if ( position === "static" ) {//如果原元素是静态定位的,改成相对定位 elem.style.position = "relative"; } curOffset = curElem.offset();//获取当前元素的offset curCSSTop = jQuery.css( elem, "top" ); curCSSLeft = jQuery.css( elem, "left" ); //如果元素的position为absolute或者fixed,而且其top和left属性中至少有一项的值是auto,calculatePosition为true 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 {//如果calculatePosition为false,元素相对定位或者原本就有top、left值 curTop = parseFloat( curCSSTop ) || 0; curLeft = parseFloat( curCSSLeft ) || 0; } if ( jQuery.isFunction( options ) ) {//如果options是函数,遍历调用 options = options.call( elem, i, curOffset ); } //计算props值,因为offset是相对文档定位,新的css的top/left值 //需要根据原来的offset和css的top/left值计算 if ( options.top != null ) { props.top = ( options.top - curOffset.top ) + curTop; } if ( options.left != null ) { props.left = ( options.left - curOffset.left ) + curLeft; } //如果有using这个参数,可以调用using方法 if ( "using" in options ) { options.using.call( elem, props ); } else {//如果没有,设置css偏移。 curElem.css( props ); } } };
关于using这个选项,之前在jQuery API中没有看到过。发现隐藏技能了(⊙o⊙)…??
好吧,刚在百度上试了下
$('#page').offset({"top":1000,"left":100,"using":function(prop){console.log(prop)}}); //Object {top: -302, left: 100}