scroll.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { isClient } from '@vueuse/core'
  2. import { getStyle } from './style'
  3. export const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => {
  4. if (!isClient) return false
  5. const key = (
  6. {
  7. undefined: 'overflow',
  8. true: 'overflow-y',
  9. false: 'overflow-x',
  10. } as const
  11. )[String(isVertical)]!
  12. const overflow = getStyle(el, key)
  13. return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s))
  14. }
  15. export const getScrollContainer = (
  16. el: HTMLElement,
  17. isVertical?: boolean
  18. ): Window | HTMLElement | undefined => {
  19. if (!isClient) return
  20. let parent: HTMLElement = el
  21. while (parent) {
  22. if ([window, document, document.documentElement].includes(parent))
  23. return window
  24. if (isScroll(parent, isVertical)) return parent
  25. parent = parent.parentNode as HTMLElement
  26. }
  27. return parent
  28. }
  29. let scrollBarWidth: number
  30. export const getScrollBarWidth = (namespace: string): number => {
  31. if (!isClient) return 0
  32. if (scrollBarWidth !== undefined) return scrollBarWidth
  33. const outer = document.createElement('div')
  34. outer.className = `${namespace}-scrollbar__wrap`
  35. outer.style.visibility = 'hidden'
  36. outer.style.width = '100px'
  37. outer.style.position = 'absolute'
  38. outer.style.top = '-9999px'
  39. document.body.appendChild(outer)
  40. const widthNoScroll = outer.offsetWidth
  41. outer.style.overflow = 'scroll'
  42. const inner = document.createElement('div')
  43. inner.style.width = '100%'
  44. outer.appendChild(inner)
  45. const widthWithScroll = inner.offsetWidth
  46. outer.parentNode?.removeChild(outer)
  47. scrollBarWidth = widthNoScroll - widthWithScroll
  48. return scrollBarWidth
  49. }
  50. /**
  51. * Scroll with in the container element, positioning the **selected** element at the top
  52. * of the container
  53. */
  54. export function scrollIntoView(
  55. container: HTMLElement,
  56. selected: HTMLElement
  57. ): void {
  58. if (!isClient) return
  59. if (!selected) {
  60. container.scrollTop = 0
  61. return
  62. }
  63. const offsetParents: HTMLElement[] = []
  64. let pointer = selected.offsetParent
  65. while (
  66. pointer !== null &&
  67. container !== pointer &&
  68. container.contains(pointer)
  69. ) {
  70. offsetParents.push(pointer as HTMLElement)
  71. pointer = (pointer as HTMLElement).offsetParent
  72. }
  73. const top =
  74. selected.offsetTop +
  75. offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0)
  76. const bottom = top + selected.offsetHeight
  77. const viewRectTop = container.scrollTop
  78. const viewRectBottom = viewRectTop + container.clientHeight
  79. if (top < viewRectTop) {
  80. container.scrollTop = top
  81. } else if (bottom > viewRectBottom) {
  82. container.scrollTop = bottom - container.clientHeight
  83. }
  84. }