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  }