-
Notifications
You must be signed in to change notification settings - Fork 21
/
index.js
252 lines (208 loc) · 7.52 KB
/
index.js
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
// Position
// --------
// 定位工具组件,将一个 DOM 节点相对对另一个 DOM 节点进行定位操作。
// 代码易改,人生难得
var Position = exports,
VIEWPORT = { _id: 'VIEWPORT', nodeType: 1 },
$ = require('jquery'),
isPinFixed = false,
ua = (window.navigator.userAgent || "").toLowerCase(),
isIE6 = ua.indexOf("msie 6") !== -1;
// 将目标元素相对于基准元素进行定位
// 这是 Position 的基础方法,接收两个参数,分别描述了目标元素和基准元素的定位点
Position.pin = function(pinObject, baseObject) {
// 将两个参数转换成标准定位对象 { element: a, x: 0, y: 0 }
pinObject = normalize(pinObject);
baseObject = normalize(baseObject);
// if pinObject.element is not present
// https://github.com/aralejs/position/pull/11
if (pinObject.element === VIEWPORT ||
pinObject.element._id === 'VIEWPORT') {
return;
}
// 设定目标元素的 position 为绝对定位
// 若元素的初始 position 不为 absolute,会影响元素的 display、宽高等属性
var pinElement = $(pinObject.element);
if (pinElement.css('position') !== 'fixed' || isIE6) {
pinElement.css('position', 'absolute');
isPinFixed = false;
}
else {
// 定位 fixed 元素的标志位,下面有特殊处理
isPinFixed = true;
}
// 将位置属性归一化为数值
// 注:必须放在上面这句 `css('position', 'absolute')` 之后,
// 否则获取的宽高有可能不对
posConverter(pinObject);
posConverter(baseObject);
var parentOffset = getParentOffset(pinElement);
var baseOffset = baseObject.offset();
// 计算目标元素的位置
var top = baseOffset.top + baseObject.y -
pinObject.y - parentOffset.top;
var left = baseOffset.left + baseObject.x -
pinObject.x - parentOffset.left;
// 定位目标元素
pinElement.css({ left: left, top: top });
};
// 将目标元素相对于基准元素进行居中定位
// 接受两个参数,分别为目标元素和定位的基准元素,都是 DOM 节点类型
Position.center = function(pinElement, baseElement) {
Position.pin({
element: pinElement,
x: '50%',
y: '50%'
}, {
element: baseElement,
x: '50%',
y: '50%'
});
};
// 这是当前可视区域的伪 DOM 节点
// 需要相对于当前可视区域定位时,可传入此对象作为 element 参数
Position.VIEWPORT = VIEWPORT;
// Helpers
// -------
// 将参数包装成标准的定位对象,形似 { element: a, x: 0, y: 0 }
function normalize(posObject) {
posObject = toElement(posObject) || {};
if (posObject.nodeType) {
posObject = { element: posObject };
}
var element = toElement(posObject.element) || VIEWPORT;
if (element.nodeType !== 1) {
throw new Error('posObject.element is invalid.');
}
var result = {
element: element,
x: posObject.x || 0,
y: posObject.y || 0
};
// config 的深度克隆会替换掉 Position.VIEWPORT, 导致直接比较为 false
var isVIEWPORT = (element === VIEWPORT || element._id === 'VIEWPORT');
// 归一化 offset
result.offset = function() {
// 若定位 fixed 元素,则父元素的 offset 没有意义
if (isPinFixed) {
return {
left: 0,
top: 0
};
}
else if (isVIEWPORT) {
return {
left: $(document).scrollLeft(),
top: $(document).scrollTop()
};
}
else {
return getOffset($(element)[0]);
}
};
// 归一化 size, 含 padding 和 border
result.size = function() {
var el = isVIEWPORT ? $(window) : $(element);
return {
width: el.outerWidth(),
height: el.outerHeight()
};
};
return result;
}
// 对 x, y 两个参数为 left|center|right|%|px 时的处理,全部处理为纯数字
function posConverter(pinObject) {
pinObject.x = xyConverter(pinObject.x, pinObject, 'width');
pinObject.y = xyConverter(pinObject.y, pinObject, 'height');
}
// 处理 x, y 值,都转化为数字
function xyConverter(x, pinObject, type) {
// 先转成字符串再说!好处理
x = x + '';
// 处理 px
x = x.replace(/px/gi, '');
// 处理 alias
if (/\D/.test(x)) {
x = x.replace(/(?:top|left)/gi, '0%')
.replace(/center/gi, '50%')
.replace(/(?:bottom|right)/gi, '100%');
}
// 将百分比转为像素值
if (x.indexOf('%') !== -1) {
//支持小数
x = x.replace(/(\d+(?:\.\d+)?)%/gi, function(m, d) {
return pinObject.size()[type] * (d / 100.0);
});
}
// 处理类似 100%+20px 的情况
if (/[+\-*\/]/.test(x)) {
try {
// eval 会影响压缩
// new Function 方法效率高于 for 循环拆字符串的方法
// 参照:http://jsperf.com/eval-newfunction-for
x = (new Function('return ' + x))();
} catch (e) {
throw new Error('Invalid position value: ' + x);
}
}
// 转回为数字
return numberize(x);
}
// 获取 offsetParent 的位置
function getParentOffset(element) {
var parent = element.offsetParent();
// IE7 下,body 子节点的 offsetParent 为 html 元素,其 offset 为
// { top: 2, left: 2 },会导致定位差 2 像素,所以这里将 parent
// 转为 document.body
if (parent[0] === document.documentElement) {
parent = $(document.body);
}
// 修正 ie6 下 absolute 定位不准的 bug
if (isIE6) {
parent.css('zoom', 1);
}
// 获取 offsetParent 的 offset
var offset;
// 当 offsetParent 为 body,
// 而且 body 的 position 是 static 时
// 元素并不按照 body 来定位,而是按 document 定位
// http://jsfiddle.net/afc163/hN9Tc/2/
// 因此这里的偏移值直接设为 0 0
if (parent[0] === document.body &&
parent.css('position') === 'static') {
offset = { top:0, left: 0 };
} else {
offset = getOffset(parent[0]);
}
// 根据基准元素 offsetParent 的 border 宽度,来修正 offsetParent 的基准位置
offset.top += numberize(parent.css('border-top-width'));
offset.left += numberize(parent.css('border-left-width'));
return offset;
}
function numberize(s) {
return parseFloat(s, 10) || 0;
}
function toElement(element) {
return $(element)[0];
}
// fix jQuery 1.7.2 offset
// document.body 的 position 是 absolute 或 relative 时
// jQuery.offset 方法无法正确获取 body 的偏移值
// -> http://jsfiddle.net/afc163/gMAcp/
// jQuery 1.9.1 已经修正了这个问题
// -> http://jsfiddle.net/afc163/gMAcp/1/
// 这里先实现一份
// 参照 kissy 和 jquery 1.9.1
// -> https://github.com/kissyteam/kissy/blob/master/src/dom/sub-modules/base/src/base/offset.js#L366
// -> https://github.com/jquery/jquery/blob/1.9.1/src/offset.js#L28
function getOffset(element) {
var box = element.getBoundingClientRect(),
docElem = document.documentElement;
// < ie8 不支持 win.pageXOffset, 则使用 docElem.scrollLeft
return {
left: box.left + (window.pageXOffset || docElem.scrollLeft) -
(docElem.clientLeft || document.body.clientLeft || 0),
top: box.top + (window.pageYOffset || docElem.scrollTop) -
(docElem.clientTop || document.body.clientTop || 0)
};
}