github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/utils/domUtils.ts (about) 1 import { isServer } from './is' 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 }