近期要写前端组件了。狂砍各种组件源代码,这里分析一款jqueryui中的posistion插件,注意,它不是jqueryui widget,首先看下源代码整体结构图

1、看到$.fn.position 是不是非常熟悉?嗯。就是将position方法挂载到原型上。然后控件就能够直接调用了,

2、$.ui.position 这个对象是。用来进行冲突推断的,什么冲突?就是元素与父容器所拥有的空间以及当前可用窗体的控件。默认情形下,假设冲突则採用反转方向的方式显示;对这一点不要吃惊。一切都是为了正常显示而用的

3、源代码里废话较多,部分能够忽略。慢慢读

/*!
* jQuery UI Position @VERSION
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/position/
*/
(function ($, undefined) { $.ui = $.ui || {}; var cachedScrollbarWidth,
max = Math.max,
abs = Math.abs,
round = Math.round,
rhorizontal = /left|center|right/,
rvertical = /top|center|bottom/,
roffset = /[\+\-]\d+(\.[\d]+)?%? /,
rposition = /^\w+/,
rpercent = /%$/, //检測是否以xx%结尾
_position = $.fn.position; //保存旧的position功能,假设须要用的话 function getOffsets(offsets, width, height) {
// debugger
// 这块代码基本是都是0,0
return [
parseFloat(offsets[ 0 ]) * ( rpercent.test(offsets[ 0 ]) ? width / 100 : 1 ), //检測是否以xx%结尾
parseFloat(offsets[ 1 ]) * ( rpercent.test(offsets[ 1 ]) ? height / 100 : 1 )
];
} function parseCss(element, property) {
// debugger
// 获取css属性方法
return parseInt($.css(element, property), 10) || 0;
} /**
* 获取元素的尺寸
* @param elem
* @returns {*}
*/
function getDimensions(elem) { var raw = elem[0];
if (raw.nodeType === 9) {//Node.DOCUMENT_NODE
return {
width: elem.width(),
height: elem.height(),
offset: { top: 0, left: 0 }
};
}
if ($.isWindow(raw)) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
};
}
if (raw.preventDefault) {
return {
width: 0,
height: 0,
offset: { top: raw.pageY, left: raw.pageX }
};
}
return {
width: elem.outerWidth(),
height: elem.outerHeight(),
offset: elem.offset()
};
} $.position = {
/**
* 计算滚动栏的宽度
* @returns {*}
*/
scrollbarWidth: function () {
// debugger if (cachedScrollbarWidth !== undefined) {
return cachedScrollbarWidth;
}
var w1, w2,
div = $("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),
innerDiv = div.children()[0]; $("body").append(div);
w1 = innerDiv.offsetWidth;
div.css("overflow", "scroll"); w2 = innerDiv.offsetWidth; if (w1 === w2) {
w2 = div[0].clientWidth;
} div.remove();
//if you want to see 'div', you can comment this line return (cachedScrollbarWidth = w1 - w2);
}, /**
* 获取滚动信息
* @param within
* @returns {{width: *, height: *}}
*/
getScrollInfo: function (within) {
// debugger var overflowX = within.isWindow || within.isDocument ? "" :
within.element.css("overflow-x"),
overflowY = within.isWindow || within.isDocument ? "" :
within.element.css("overflow-y"),
hasOverflowX = overflowX === "scroll" ||
( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
hasOverflowY = overflowY === "scroll" ||
( overflowY === "auto" && within.height < within.element[0].scrollHeight );
return {
width: hasOverflowY ? $.position.scrollbarWidth() : 0,
height: hasOverflowX ? $.position.scrollbarWidth() : 0
};
},
/**
* 获取内部信息。封装过一层
* @param element
* @returns {{element: (*|HTMLElement), isWindow: *, isDocument: boolean, offset: (*|{left: number, top: number}), scrollLeft: (jQuery.scrollLeft|*), scrollTop: (jQuery.scrollTop|*), width: *, height: *}}
*/
getWithinInfo: function (element) {
// debugger var withinElement = $(element || window),
isWindow = $.isWindow(withinElement[0]),
isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9;
return {
element: withinElement,
isWindow: isWindow,
isDocument: isDocument,
offset: withinElement.offset() || { left: 0, top: 0 },
scrollLeft: withinElement.scrollLeft(),
scrollTop: withinElement.scrollTop(),
width: isWindow ? withinElement.width() : withinElement.outerWidth(),
height: isWindow ? withinElement.height() : withinElement.outerHeight()
};
}
}; $.fn.position = function (options) { debugger
console.log('invoke position'); if (!options || !options.of) {
//第二次是jQuery.offset.setOffset:function( elem, options, i )中发起的调用 options中没有of属性F
//
return _position.apply(this, arguments); //_position为原始的$.fn.position(jquery中,非jqueryui中的)
} console.log('invoke position do something'); // make a copy, we don't want to modify arguments
options = $.extend({}, options); var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
target = $(options.of), //父容器
within = $.position.getWithinInfo(options.within),
scrollInfo = $.position.getScrollInfo(within),
collision = ( options.collision || "flip" ).split(" "),
offsets = {}; dimensions = getDimensions(target);//父容器尺寸
if (target[0].preventDefault) {
// force left top to allow flipping
options.at = "left top";
}
targetWidth = dimensions.width;
targetHeight = dimensions.height;
targetOffset = dimensions.offset;
// clone to reuse original targetOffset later
basePosition = $.extend({}, targetOffset); //父容器的基准偏移量 // force my and at to have valid horizontal and vertical positions
// if a value is missing or invalid, it will be converted to center
//pass it
$.each([ "my", "at" ], function () {
var pos = ( options[ this ] || "" ).split(" "),
horizontalOffset,
verticalOffset; if (pos.length === 1) {
pos = rhorizontal.test(pos[ 0 ]) ?
pos.concat([ "center" ]) :
rvertical.test(pos[ 0 ]) ?
[ "center" ].concat(pos) :
[ "center", "center" ];
}
pos[ 0 ] = rhorizontal.test(pos[ 0 ]) ? pos[ 0 ] : "center";
pos[ 1 ] = rvertical.test(pos[ 1 ]) ? pos[ 1 ] : "center"; // calculate offsets
// debugger
// 这里能够带有运算
horizontalOffset = roffset.exec(pos[ 0 ]);
verticalOffset = roffset.exec(pos[ 1 ]);
offsets[ this ] = [
horizontalOffset ? horizontalOffset[ 0 ] : 0,
verticalOffset ? verticalOffset[ 0 ] : 0
]; // reduce to just the positions without the offsets
options[ this ] = [
rposition.exec(pos[ 0 ])[ 0 ],
rposition.exec(pos[ 1 ])[ 0 ]
];
}); // normalize collision option
if (collision.length === 1) {
collision[ 1 ] = collision[ 0 ]; //假设仅仅有一个元素,则collision中的两个值都是一样的
} //父元素的‘水平’位置进行定位。计算值中默认是left。所以处理下right、center
if (options.at[ 0 ] === "right") {
basePosition.left += targetWidth;
} else if (options.at[ 0 ] === "center") {
basePosition.left += targetWidth / 2;
} //父元素的‘垂直’位置进行定位。
if (options.at[ 1 ] === "bottom") {
basePosition.top += targetHeight;
} else if (options.at[ 1 ] === "center") {
basePosition.top += targetHeight / 2;
} atOffset = getOffsets(offsets.at, targetWidth, targetHeight);
basePosition.left += atOffset[ 0 ];
basePosition.top += atOffset[ 1 ]; //this is jQuery Object. This is a jQuery plugin ,not jqueryui widget!
return this.each(function () {
// debugger
var collisionPosition, using,
elem = $(this), //jQuery object
elemWidth = elem.outerWidth(), //包含内边距和边框
elemHeight = elem.outerHeight(),
marginLeft = parseCss(this, "marginLeft"),
marginTop = parseCss(this, "marginTop"),
collisionWidth = elemWidth + marginLeft + parseCss(this, "marginRight") + scrollInfo.width,
collisionHeight = elemHeight + marginTop + parseCss(this, "marginBottom") + scrollInfo.height,
position = $.extend({}, basePosition),
myOffset = getOffsets(offsets.my, elem.outerWidth(), elem.outerHeight());
//my: $( "#my_horizontal" ).val() + "+5 " + $( "#my_vertical" ).val()+'+5',
// 定义添加的 //子(目标)元素的‘水平’位置定位计算
if (options.my[ 0 ] === "right") {
position.left -= elemWidth;
} else if (options.my[ 0 ] === "center") {
position.left -= elemWidth / 2;
} //子(目标)元素的‘垂直’位置定位计算
if (options.my[ 1 ] === "bottom") {
position.top -= elemHeight;
} else if (options.my[ 1 ] === "center") {
position.top -= elemHeight / 2;
} //加上用户自己定义变化量
position.left += myOffset[ 0 ];
position.top += myOffset[ 1 ]; // if the browser doesn't support fractions, then round for consistent results
if (!$.support.offsetFractions) { //假设浏览器不支持小数。则四舍五入
position.left = round(position.left);
position.top = round(position.top);
} collisionPosition = {
marginLeft: marginLeft,
marginTop: marginTop
}; //这里为什么会仅仅遍历left,top。由于仅仅要有left和top,我们就能够进行定位了
$.each([ "left", "top" ], function (i, dir) { //collision is ['flip'] by default
if ($.ui.position[ collision[ i ] ]) {
$.ui.position[ collision[ i ] ][ dir ](position, {
targetWidth: targetWidth,
targetHeight: targetHeight,
elemWidth: elemWidth,
elemHeight: elemHeight,
collisionPosition: collisionPosition,
collisionWidth: collisionWidth,
collisionHeight: collisionHeight,
offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
my: options.my,
at: options.at,
within: within,
elem: elem
});
} }); if (options.using) {
// adds feedback as second argument to using callback, if present
using = function (props) {
var left = targetOffset.left - position.left,
right = left + targetWidth - elemWidth,
top = targetOffset.top - position.top,
bottom = top + targetHeight - elemHeight,
feedback = {
target: {
element: target,
left: targetOffset.left,
top: targetOffset.top,
width: targetWidth,
height: targetHeight
},
element: {
element: elem,
left: position.left,
top: position.top,
width: elemWidth,
height: elemHeight
},
horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
};
if (targetWidth < elemWidth && abs(left + right) < targetWidth) {
feedback.horizontal = "center";
}
if (targetHeight < elemHeight && abs(top + bottom) < targetHeight) {
feedback.vertical = "middle";
}
if (max(abs(left), abs(right)) > max(abs(top), abs(bottom))) {
feedback.important = "horizontal";
} else {
feedback.important = "vertical";
}
options.using.call(this, props, feedback);
};
} console.log(position); elem.offset($.extend(position, { using: using }));
});
}; /**
* 进行冲突推断的解决方式
*
* @type {{fit: {left: Function, top: Function}, flip: {left: Function, top: Function}, flipfit: {left: Function, top: Function}}}
*/
$.ui.position = {
fit: {
left: function (position, data) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
outerWidth = within.width,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = withinOffset - collisionPosLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
newOverRight; // element is wider than within
if (data.collisionWidth > outerWidth) {
// element is initially over the left side of within
if (overLeft > 0 && overRight <= 0) {
newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
position.left += overLeft - newOverRight;
// element is initially over right side of within
} else if (overRight > 0 && overLeft <= 0) {
position.left = withinOffset;
// element is initially over both left and right sides of within
} else {
if (overLeft > overRight) {
position.left = withinOffset + outerWidth - data.collisionWidth;
} else {
position.left = withinOffset;
}
}
// too far left -> align with left edge
} else if (overLeft > 0) {
position.left += overLeft;
// too far right -> align with right edge
} else if (overRight > 0) {
position.left -= overRight;
// adjust based on position and margin
} else {
position.left = max(position.left - collisionPosLeft, position.left);
}
},
top: function (position, data) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
outerHeight = data.within.height,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = withinOffset - collisionPosTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
newOverBottom; // element is taller than within
if (data.collisionHeight > outerHeight) {
// element is initially over the top of within
if (overTop > 0 && overBottom <= 0) {
newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
position.top += overTop - newOverBottom;
// element is initially over bottom of within
} else if (overBottom > 0 && overTop <= 0) {
position.top = withinOffset;
// element is initially over both top and bottom of within
} else {
if (overTop > overBottom) {
position.top = withinOffset + outerHeight - data.collisionHeight;
} else {
position.top = withinOffset;
}
}
// too far up -> align with top
} else if (overTop > 0) {
position.top += overTop;
// too far down -> align with bottom edge
} else if (overBottom > 0) {
position.top -= overBottom;
// adjust based on position and margin
} else {
position.top = max(position.top - collisionPosTop, position.top);
}
}
},
flip: {
left: function (position, data) {
debugger var within = data.within,
withinOffset = within.offset.left + within.scrollLeft,
outerWidth = within.width,
offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = collisionPosLeft - offsetLeft, //judgement
overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,//judgement myOffset = data.my[ 0 ] === "left" ? -data.elemWidth : data.my[ 0 ] === "right" ? data.elemWidth : 0,
atOffset = data.at[ 0 ] === "left" ? data.targetWidth : data.at[ 0 ] === "right" ? -data.targetWidth : 0,
offset = -2 * data.offset[ 0 ],
newOverRight,
newOverLeft; if (overLeft < 0) {
newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
if (newOverRight < 0 || newOverRight < abs(overLeft)) {
position.left += myOffset + atOffset + offset;
}
}
else if (overRight > 0) {
newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
if (newOverLeft > 0 || abs(newOverLeft) < overRight) {
position.left += myOffset + atOffset + offset;
}
}
},
top: function (position, data) { debugger var within = data.within,
withinOffset = within.offset.top + within.scrollTop,
outerHeight = within.height,
offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = collisionPosTop - offsetTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, top = data.my[ 1 ] === "top", myOffset = top ? -data.elemHeight : data.my[ 1 ] === "bottom" ? data.elemHeight : 0,
atOffset = data.at[ 1 ] === "top" ? data.targetHeight : data.at[ 1 ] === "bottom" ? -data.targetHeight : 0, offset = -2 * data.offset[ 1 ],
newOverTop,
newOverBottom; if (overTop < 0) {
newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
if (( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs(overTop) )) {
position.top += myOffset + atOffset + offset;
}
}
else if (overBottom > 0) {
newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
if (( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs(newOverTop) < overBottom )) {
position.top += myOffset + atOffset + offset;
}
}
}
},
flipfit: {
left: function () {
$.ui.position.flip.left.apply(this, arguments);
$.ui.position.fit.left.apply(this, arguments);
},
top: function () {
$.ui.position.flip.top.apply(this, arguments);
$.ui.position.fit.top.apply(this, arguments);
}
}
}; // fraction support test
(function () {
var testElement, testElementParent, testElementStyle, offsetLeft, i,
body = document.getElementsByTagName("body")[ 0 ],
div = document.createElement("div"); //Create a "fake body" for testing based on method used in jQuery.support
testElement = document.createElement(body ? "div" : "body");
testElementStyle = {
visibility: "hidden",
width: 0,
height: 0,
border: 0,
margin: 0,
background: "none"
};
if (body) {
$.extend(testElementStyle, {
position: "absolute",
left: "-1000px",
top: "-1000px"
});
}
for (i in testElementStyle) {
testElement.style[ i ] = testElementStyle[ i ];
}
testElement.appendChild(div);
testElementParent = body || document.documentElement;
testElementParent.insertBefore(testElement, testElementParent.firstChild); div.style.cssText = "position: absolute; left: 10.7432222px;"; offsetLeft = $(div).offset().left;
$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; testElement.innerHTML = "";
testElementParent.removeChild(testElement);
})(); }(jQuery) );

jqueryui.position.js源代码分析的更多相关文章

  1. easyloader.js源代码分析

    http://www.cnblogs.com/jasonoiu/p/easyloader_source_code_analysis.html Jquery easyui是一个javascript UI ...

  2. android-plugmgr源代码分析

    android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...

  3. 转:SDL2源代码分析

    1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...

  4. 转:RTMPDump源代码分析

    0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...

  5. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  6. Hadoop源代码分析

    http://wenku.baidu.com/link?url=R-QoZXhc918qoO0BX6eXI9_uPU75whF62vFFUBIR-7c5XAYUVxDRX5Rs6QZR9hrBnUdM ...

  7. Android系统默认Home应用程序(Launcher)的启动过程源代码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还需要有一个 Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home ...

  8. pomelo源代码分析(一)

    千里之行始于足下,一直说想了解pomelo,对pomelo有兴趣,但一直迟迟没有去碰,尽管对pomelo进行源代码分析,在网络上肯定不止我一个,已经有非常优秀的前辈走在前面,如http://golan ...

  9. Android万能适配器base-adapter-helper的源代码分析

    项目地址:https://github.com/JoanZapata/base-adapter-helper 1. 功能介绍 1.1. base-adapter-helper base-adapter ...

随机推荐

  1. 【HDOJ】3386 Final Kichiku “Lanlanshu”

    数位DP.需要注意的是需要特殊处理前导0,另外连续的==匹配,不要计重了,尽量贪心的匹配掉. /* 3886 */ #include <iostream> #include <sst ...

  2. 【转】The final local variable xxx cannot be assigned, since it is defined in an enclosing type

    文地址:http://blog.163.com/benben_long/blog/static/199458243201481102257544/ 本文就自己编程时候遇到的一个问题,简要描述一下,并提 ...

  3. 1067. Disk Tree(字符串)

    1067 破题啊  写完发现理解错题意了 子目录下会有跟之前重名的 把输入的字符串存下来 排下序 然后依次找跟上面有没有重的 #include <iostream> #include< ...

  4. input之placeholder与行高的问题。

    我们实现一个输入框的视觉的时候为了保持其各种各样的兼容性: 1.鼠标要跟文字一样高度. 2.文字要居中对齐. 3.还要有placeholder 第一个目标,当实现一个高度为40像素的高度输入框时,为了 ...

  5. CSS sprites 技术

    Css Sprites 技术逐渐流行,各大网站上都可以看到它的身影. 但从本质上,Css Sprites 只是 Css 技术的一个使用小窍门,初学者也能快速上手. Css Sprites 简单解释: ...

  6. SpringContextHolder 静态持有SpringContext的引用(如何取得Spring管理的bean )

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 3 ...

  7. 使用BusyBox制作嵌入式Linux根文件系统

    STEP 1:构建目录结构  创建根文件系统目录,主要包括以下目录/dev  /etc /lib  /usr  /var /proc /tmp /home /root /mnt /bin  /sbin ...

  8. tpl + ccr

    不是非此即彼的场景.如下混合使用CCR+TPL的代码说明问题:It's not an either/or scenario.You can intermix CCR and TPL code like ...

  9. SQL Server 触发器:表的特定字段更新时,触发Update触发器

    create trigger TR_MasterTable_Updateon MasterTableafter updateas if update ([Type])--当Type字段被更新时,才会触 ...

  10. POJ2115 C Looooops 模线性方程(扩展欧几里得)

    题意:很明显,我就不说了 分析:令n=2^k,因为A,B,C<n,所以取模以后不会变化,所以就是求(A+x*C)%n=B 转化一下就是求 C*x=B-A(%n),最小的x 令a=C,b=B-A ...