scroll.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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)) return window;
  23. if (isScroll(parent, isVertical)) return parent;
  24. parent = parent.parentNode as HTMLElement;
  25. }
  26. return parent;
  27. };
  28. let scrollBarWidth: number;
  29. export const getScrollBarWidth = (namespace: string): number => {
  30. if (!isClient) return 0;
  31. if (scrollBarWidth !== undefined) return scrollBarWidth;
  32. const outer = document.createElement('div');
  33. outer.className = `${namespace}-scrollbar__wrap`;
  34. outer.style.visibility = 'hidden';
  35. outer.style.width = '100px';
  36. outer.style.position = 'absolute';
  37. outer.style.top = '-9999px';
  38. document.body.appendChild(outer);
  39. const widthNoScroll = outer.offsetWidth;
  40. outer.style.overflow = 'scroll';
  41. const inner = document.createElement('div');
  42. inner.style.width = '100%';
  43. outer.appendChild(inner);
  44. const widthWithScroll = inner.offsetWidth;
  45. outer.parentNode?.removeChild(outer);
  46. scrollBarWidth = widthNoScroll - widthWithScroll;
  47. return scrollBarWidth;
  48. };
  49. /**
  50. * Scroll with in the container element, positioning the **selected** element at the top
  51. * of the container
  52. */
  53. export function scrollIntoView(container: HTMLElement, selected: HTMLElement): void {
  54. if (!isClient) return;
  55. if (!selected) {
  56. container.scrollTop = 0;
  57. return;
  58. }
  59. const offsetParents: HTMLElement[] = [];
  60. let pointer = selected.offsetParent;
  61. while (pointer !== null && container !== pointer && container.contains(pointer)) {
  62. offsetParents.push(pointer as HTMLElement);
  63. pointer = (pointer as HTMLElement).offsetParent;
  64. }
  65. const top = selected.offsetTop + offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0);
  66. const bottom = top + selected.offsetHeight;
  67. const viewRectTop = container.scrollTop;
  68. const viewRectBottom = viewRectTop + container.clientHeight;
  69. if (top < viewRectTop) {
  70. container.scrollTop = top;
  71. } else if (bottom > viewRectBottom) {
  72. container.scrollTop = bottom - container.clientHeight;
  73. }
  74. }