提交 | 用户 | 时间
|
820397
|
1 |
import { isServer } from './is' |
H |
2 |
const ieVersion = isServer ? 0 : Number((document as any).documentMode) |
|
3 |
const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g |
|
4 |
const MOZ_HACK_REGEXP = /^moz([A-Z])/ |
|
5 |
|
|
6 |
export interface ViewportOffsetResult { |
|
7 |
left: number |
|
8 |
top: number |
|
9 |
right: number |
|
10 |
bottom: number |
|
11 |
rightIncludeBody: number |
|
12 |
bottomIncludeBody: number |
|
13 |
} |
|
14 |
|
|
15 |
/* istanbul ignore next */ |
|
16 |
const trim = function (string: string) { |
|
17 |
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '') |
|
18 |
} |
|
19 |
|
|
20 |
/* istanbul ignore next */ |
|
21 |
const camelCase = function (name: string) { |
|
22 |
return name |
|
23 |
.replace(SPECIAL_CHARS_REGEXP, function (_, __, letter, offset) { |
|
24 |
return offset ? letter.toUpperCase() : letter |
|
25 |
}) |
|
26 |
.replace(MOZ_HACK_REGEXP, 'Moz$1') |
|
27 |
} |
|
28 |
|
|
29 |
/* istanbul ignore next */ |
|
30 |
export function hasClass(el: Element, cls: string) { |
|
31 |
if (!el || !cls) return false |
|
32 |
if (cls.indexOf(' ') !== -1) { |
|
33 |
throw new Error('className should not contain space.') |
|
34 |
} |
|
35 |
if (el.classList) { |
|
36 |
return el.classList.contains(cls) |
|
37 |
} else { |
|
38 |
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1 |
|
39 |
} |
|
40 |
} |
|
41 |
|
|
42 |
/* istanbul ignore next */ |
|
43 |
export function addClass(el: Element, cls: string) { |
|
44 |
if (!el) return |
|
45 |
let curClass = el.className |
|
46 |
const classes = (cls || '').split(' ') |
|
47 |
|
|
48 |
for (let i = 0, j = classes.length; i < j; i++) { |
|
49 |
const clsName = classes[i] |
|
50 |
if (!clsName) continue |
|
51 |
|
|
52 |
if (el.classList) { |
|
53 |
el.classList.add(clsName) |
|
54 |
} else if (!hasClass(el, clsName)) { |
|
55 |
curClass += ' ' + clsName |
|
56 |
} |
|
57 |
} |
|
58 |
if (!el.classList) { |
|
59 |
el.className = curClass |
|
60 |
} |
|
61 |
} |
|
62 |
|
|
63 |
/* istanbul ignore next */ |
|
64 |
export function removeClass(el: Element, cls: string) { |
|
65 |
if (!el || !cls) return |
|
66 |
const classes = cls.split(' ') |
|
67 |
let curClass = ' ' + el.className + ' ' |
|
68 |
|
|
69 |
for (let i = 0, j = classes.length; i < j; i++) { |
|
70 |
const clsName = classes[i] |
|
71 |
if (!clsName) continue |
|
72 |
|
|
73 |
if (el.classList) { |
|
74 |
el.classList.remove(clsName) |
|
75 |
} else if (hasClass(el, clsName)) { |
|
76 |
curClass = curClass.replace(' ' + clsName + ' ', ' ') |
|
77 |
} |
|
78 |
} |
|
79 |
if (!el.classList) { |
|
80 |
el.className = trim(curClass) |
|
81 |
} |
|
82 |
} |
|
83 |
|
|
84 |
export function getBoundingClientRect(element: Element): DOMRect | number { |
|
85 |
if (!element || !element.getBoundingClientRect) { |
|
86 |
return 0 |
|
87 |
} |
|
88 |
return element.getBoundingClientRect() |
|
89 |
} |
|
90 |
|
|
91 |
/** |
|
92 |
* 获取当前元素的left、top偏移 |
|
93 |
* left:元素最左侧距离文档左侧的距离 |
|
94 |
* top:元素最顶端距离文档顶端的距离 |
|
95 |
* right:元素最右侧距离文档右侧的距离 |
|
96 |
* bottom:元素最底端距离文档底端的距离 |
|
97 |
* rightIncludeBody:元素最左侧距离文档右侧的距离 |
|
98 |
* bottomIncludeBody:元素最底端距离文档最底部的距离 |
|
99 |
* |
|
100 |
* @description: |
|
101 |
*/ |
|
102 |
export function getViewportOffset(element: Element): ViewportOffsetResult { |
|
103 |
const doc = document.documentElement |
|
104 |
|
|
105 |
const docScrollLeft = doc.scrollLeft |
|
106 |
const docScrollTop = doc.scrollTop |
|
107 |
const docClientLeft = doc.clientLeft |
|
108 |
const docClientTop = doc.clientTop |
|
109 |
|
|
110 |
const pageXOffset = window.pageXOffset |
|
111 |
const pageYOffset = window.pageYOffset |
|
112 |
|
|
113 |
const box = getBoundingClientRect(element) |
|
114 |
|
|
115 |
const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect |
|
116 |
|
|
117 |
const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0) |
|
118 |
const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0) |
|
119 |
const offsetLeft = retLeft + pageXOffset |
|
120 |
const offsetTop = rectTop + pageYOffset |
|
121 |
|
|
122 |
const left = offsetLeft - scrollLeft |
|
123 |
const top = offsetTop - scrollTop |
|
124 |
|
|
125 |
const clientWidth = window.document.documentElement.clientWidth |
|
126 |
const clientHeight = window.document.documentElement.clientHeight |
|
127 |
return { |
|
128 |
left: left, |
|
129 |
top: top, |
|
130 |
right: clientWidth - rectWidth - left, |
|
131 |
bottom: clientHeight - rectHeight - top, |
|
132 |
rightIncludeBody: clientWidth - left, |
|
133 |
bottomIncludeBody: clientHeight - top |
|
134 |
} |
|
135 |
} |
|
136 |
|
|
137 |
/* istanbul ignore next */ |
|
138 |
export const on = function ( |
|
139 |
element: HTMLElement | Document | Window, |
|
140 |
event: string, |
|
141 |
handler: EventListenerOrEventListenerObject |
|
142 |
): void { |
|
143 |
if (element && event && handler) { |
|
144 |
element.addEventListener(event, handler, false) |
|
145 |
} |
|
146 |
} |
|
147 |
|
|
148 |
/* istanbul ignore next */ |
|
149 |
export const off = function ( |
|
150 |
element: HTMLElement | Document | Window, |
|
151 |
event: string, |
|
152 |
handler: any |
|
153 |
): void { |
|
154 |
if (element && event && handler) { |
|
155 |
element.removeEventListener(event, handler, false) |
|
156 |
} |
|
157 |
} |
|
158 |
|
|
159 |
/* istanbul ignore next */ |
|
160 |
export const once = function (el: HTMLElement, event: string, fn: EventListener): void { |
|
161 |
const listener = function (this: any, ...args: unknown[]) { |
|
162 |
if (fn) { |
|
163 |
// @ts-ignore |
|
164 |
fn.apply(this, args) |
|
165 |
} |
|
166 |
off(el, event, listener) |
|
167 |
} |
|
168 |
on(el, event, listener) |
|
169 |
} |
|
170 |
|
|
171 |
/* istanbul ignore next */ |
|
172 |
export const getStyle = |
|
173 |
ieVersion < 9 |
|
174 |
? function (element: Element | any, styleName: string) { |
|
175 |
if (isServer) return |
|
176 |
if (!element || !styleName) return null |
|
177 |
styleName = camelCase(styleName) |
|
178 |
if (styleName === 'float') { |
|
179 |
styleName = 'styleFloat' |
|
180 |
} |
|
181 |
try { |
|
182 |
switch (styleName) { |
|
183 |
case 'opacity': |
|
184 |
try { |
|
185 |
return element.filters.item('alpha').opacity / 100 |
|
186 |
} catch (e) { |
|
187 |
return 1.0 |
|
188 |
} |
|
189 |
default: |
|
190 |
return element.style[styleName] || element.currentStyle |
|
191 |
? element.currentStyle[styleName] |
|
192 |
: null |
|
193 |
} |
|
194 |
} catch (e) { |
|
195 |
return element.style[styleName] |
|
196 |
} |
|
197 |
} |
|
198 |
: function (element: Element | any, styleName: string) { |
|
199 |
if (isServer) return |
|
200 |
if (!element || !styleName) return null |
|
201 |
styleName = camelCase(styleName) |
|
202 |
if (styleName === 'float') { |
|
203 |
styleName = 'cssFloat' |
|
204 |
} |
|
205 |
try { |
|
206 |
const computed = (document as any).defaultView.getComputedStyle(element, '') |
|
207 |
return element.style[styleName] || computed ? computed[styleName] : null |
|
208 |
} catch (e) { |
|
209 |
return element.style[styleName] |
|
210 |
} |
|
211 |
} |
|
212 |
|
|
213 |
/* istanbul ignore next */ |
|
214 |
export function setStyle(element: Element | any, styleName: any, value: any) { |
|
215 |
if (!element || !styleName) return |
|
216 |
|
|
217 |
if (typeof styleName === 'object') { |
|
218 |
for (const prop in styleName) { |
|
219 |
if (Object.prototype.hasOwnProperty.call(styleName, prop)) { |
|
220 |
setStyle(element, prop, styleName[prop]) |
|
221 |
} |
|
222 |
} |
|
223 |
} else { |
|
224 |
styleName = camelCase(styleName) |
|
225 |
if (styleName === 'opacity' && ieVersion < 9) { |
|
226 |
element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')' |
|
227 |
} else { |
|
228 |
element.style[styleName] = value |
|
229 |
} |
|
230 |
} |
|
231 |
} |
|
232 |
|
|
233 |
/* istanbul ignore next */ |
|
234 |
export const isScroll = (el: Element, vertical: any) => { |
|
235 |
if (isServer) return |
|
236 |
|
|
237 |
const determinedDirection = vertical !== null || vertical !== undefined |
|
238 |
const overflow = determinedDirection |
|
239 |
? vertical |
|
240 |
? getStyle(el, 'overflow-y') |
|
241 |
: getStyle(el, 'overflow-x') |
|
242 |
: getStyle(el, 'overflow') |
|
243 |
|
|
244 |
return overflow.match(/(scroll|auto)/) |
|
245 |
} |
|
246 |
|
|
247 |
/* istanbul ignore next */ |
|
248 |
export const getScrollContainer = (el: Element, vertical?: any) => { |
|
249 |
if (isServer) return |
|
250 |
|
|
251 |
let parent: any = el |
|
252 |
while (parent) { |
|
253 |
if ([window, document, document.documentElement].includes(parent)) { |
|
254 |
return window |
|
255 |
} |
|
256 |
if (isScroll(parent, vertical)) { |
|
257 |
return parent |
|
258 |
} |
|
259 |
parent = parent.parentNode |
|
260 |
} |
|
261 |
|
|
262 |
return parent |
|
263 |
} |
|
264 |
|
|
265 |
/* istanbul ignore next */ |
|
266 |
export const isInContainer = (el: Element, container: any) => { |
|
267 |
if (isServer || !el || !container) return false |
|
268 |
|
|
269 |
const elRect = el.getBoundingClientRect() |
|
270 |
let containerRect |
|
271 |
|
|
272 |
if ([window, document, document.documentElement, null, undefined].includes(container)) { |
|
273 |
containerRect = { |
|
274 |
top: 0, |
|
275 |
right: window.innerWidth, |
|
276 |
bottom: window.innerHeight, |
|
277 |
left: 0 |
|
278 |
} |
|
279 |
} else { |
|
280 |
containerRect = container.getBoundingClientRect() |
|
281 |
} |
|
282 |
|
|
283 |
return ( |
|
284 |
elRect.top < containerRect.bottom && |
|
285 |
elRect.bottom > containerRect.top && |
|
286 |
elRect.right > containerRect.left && |
|
287 |
elRect.left < containerRect.right |
|
288 |
) |
|
289 |
} |