潘志宝
2024-09-18 6d9c089cebac440c78573e9fa95190ee9ead674c
提交 | 用户 | 时间
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 }