-
Notifications
You must be signed in to change notification settings - Fork 24
/
overlay.js
250 lines (205 loc) · 5.93 KB
/
overlay.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
const $ = require("jquery");
const Position = require("position");
const Shim = require("arale-iframe-shim");
const Widget = require("arale-widget");
// Overlay
// -------
// Overlay 组件的核心特点是可定位(Positionable)和可层叠(Stackable)
// 是一切悬浮类 UI 组件的基类
const Overlay = Widget.extend({
attrs: {
// 基本属性
width: null,
height: null,
zIndex: 99,
visible: false,
// 定位配置
align: {
// element 的定位点,默认为左上角
selfXY: [0, 0],
// 基准定位元素,默认为当前可视区域
baseElement: Position.VIEWPORT,
// 基准定位元素的定位点,默认为左上角
baseXY: [0, 0]
},
// 父元素
parentNode: document.body
},
show: function () {
// 若从未渲染,则调用 render
if (!this.rendered) {
this.render();
}
this.set('visible', true);
return this;
},
hide: function () {
this.set('visible', false);
return this;
},
setup: function () {
var that = this;
// 加载 iframe 遮罩层并与 overlay 保持同步
this._setupShim();
// 窗口resize时,重新定位浮层
this._setupResize();
this.after('render', function () {
var _pos = this.element.css('position');
if (_pos === 'static' || _pos === 'relative') {
this.element.css({
position: 'absolute',
left: '-9999px',
top: '-9999px'
});
}
});
// 统一在显示之后重新设定位置
this.after('show', function () {
that._setPosition();
});
},
destroy: function () {
// 销毁两个静态数组中的实例
erase(this, Overlay.allOverlays);
erase(this, Overlay.blurOverlays);
return Overlay.superclass.destroy.call(this);
},
// 进行定位
_setPosition: function (align) {
// 不在文档流中,定位无效
if (!isInDocument(this.element[0])) return;
align || (align = this.get('align'));
// 如果align为空,表示不需要使用js对齐
if (!align) return;
var isHidden = this.element.css('display') === 'none';
// 在定位时,为避免元素高度不定,先显示出来
if (isHidden) {
this.element.css({
visibility: 'hidden',
display: 'block'
});
}
Position.pin({
element: this.element,
x: align.selfXY[0],
y: align.selfXY[1]
}, {
element: align.baseElement,
x: align.baseXY[0],
y: align.baseXY[1]
});
// 定位完成后,还原
if (isHidden) {
this.element.css({
visibility: '',
display: 'none'
});
}
return this;
},
// 加载 iframe 遮罩层并与 overlay 保持同步
_setupShim: function () {
var shim = new Shim(this.element);
// 在隐藏和设置位置后,要重新定位
// 显示后会设置位置,所以不用绑定 shim.sync
this.after('hide _setPosition', shim.sync, shim);
// 除了 parentNode 之外的其他属性发生变化时,都触发 shim 同步
var attrs = ['width', 'height'];
for (var attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
this.on('change:' + attr, shim.sync, shim);
}
}
// 在销魂自身前要销毁 shim
this.before('destroy', shim.destroy, shim);
},
// resize窗口时重新定位浮层,用这个方法收集所有浮层实例
_setupResize: function () {
Overlay.allOverlays.push(this);
},
// 除了 element 和 relativeElements,点击 body 后都会隐藏 element
_blurHide: function (arr) {
arr = $.makeArray(arr);
arr.push(this.element);
this._relativeElements = arr;
Overlay.blurOverlays.push(this);
},
// 用于 set 属性后的界面更新
_onRenderWidth: function (val) {
this.element.css('width', val);
},
_onRenderHeight: function (val) {
this.element.css('height', val);
},
_onRenderZIndex: function (val) {
this.element.css('zIndex', val);
},
_onRenderAlign: function (val) {
this._setPosition(val);
},
_onRenderVisible: function (val) {
this.element[val ? 'show' : 'hide']();
}
});
// 绑定 blur 隐藏事件
Overlay.blurOverlays = [];
$(document).on('click', function (e) {
hideBlurOverlays(e);
});
// 绑定 resize 重新定位事件
var timeout;
var winWidth = $(window).width();
var winHeight = $(window).height();
Overlay.allOverlays = [];
$(window).resize(function () {
timeout && clearTimeout(timeout);
timeout = setTimeout(function () {
var winNewWidth = $(window).width();
var winNewHeight = $(window).height();
// IE678 莫名其妙触发 resize
// http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
if (winWidth !== winNewWidth || winHeight !== winNewHeight) {
$(Overlay.allOverlays).each(function (i, item) {
// 当实例为空或隐藏时,不处理
if (!item || !item.get('visible')) {
return;
}
item._setPosition();
});
}
winWidth = winNewWidth;
winHeight = winNewHeight;
}, 80);
});
module.exports = Overlay;
// Helpers
// -------
function isInDocument(element) {
return $.contains(document.documentElement, element);
}
function hideBlurOverlays(e) {
$(Overlay.blurOverlays).each(function (index, item) {
// 当实例为空或隐藏时,不处理
if (!item || !item.get('visible')) {
return;
}
// 遍历 _relativeElements ,当点击的元素落在这些元素上时,不处理
for (var i = 0; i < item._relativeElements.length; i++) {
var el = $(item._relativeElements[i])[0];
if (el === e.target || $.contains(el, e.target)) {
return;
}
}
// 到这里,判断触发了元素的 blur 事件,隐藏元素
item.hide();
});
}
// 从数组中删除对应元素
function erase(target, array) {
for (var i = 0; i < array.length; i++) {
if (target === array[i]) {
array.splice(i, 1);
return array;
}
}
}