c8ac077897fe190f3672eab8d34a0cb034ff65d29d582999b35c63c56205472e4bccf57b2c46ce15c6d4613e56043e8cfe0c94ea28436aedbf38485efd88ae 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { defineComponent, computed, ref, reactive, unref, watch, onBeforeUnmount, h, withModifiers } from 'vue';
  2. import { HORIZONTAL, ScrollbarDirKey, SCROLLBAR_MIN_SIZE } from '../defaults.mjs';
  3. import { virtualizedScrollbarProps } from '../props.mjs';
  4. import { renderThumbStyle } from '../utils.mjs';
  5. import { BAR_MAP } from '../../../scrollbar/src/util.mjs';
  6. import { useNamespace } from '../../../../hooks/use-namespace/index.mjs';
  7. import { cAF, rAF } from '../../../../utils/raf.mjs';
  8. const ScrollBar = defineComponent({
  9. name: "ElVirtualScrollBar",
  10. props: virtualizedScrollbarProps,
  11. emits: ["scroll", "start-move", "stop-move"],
  12. setup(props, { emit }) {
  13. const GAP = computed(() => props.startGap + props.endGap);
  14. const nsVirtualScrollbar = useNamespace("virtual-scrollbar");
  15. const nsScrollbar = useNamespace("scrollbar");
  16. const trackRef = ref();
  17. const thumbRef = ref();
  18. let frameHandle = null;
  19. let onselectstartStore = null;
  20. const state = reactive({
  21. isDragging: false,
  22. traveled: 0
  23. });
  24. const bar = computed(() => BAR_MAP[props.layout]);
  25. const trackSize = computed(() => props.clientSize - unref(GAP));
  26. const trackStyle = computed(() => ({
  27. position: "absolute",
  28. width: `${HORIZONTAL === props.layout ? trackSize.value : props.scrollbarSize}px`,
  29. height: `${HORIZONTAL === props.layout ? props.scrollbarSize : trackSize.value}px`,
  30. [ScrollbarDirKey[props.layout]]: "2px",
  31. right: "2px",
  32. bottom: "2px",
  33. borderRadius: "4px"
  34. }));
  35. const thumbSize = computed(() => {
  36. const ratio = props.ratio;
  37. if (ratio >= 100) {
  38. return Number.POSITIVE_INFINITY;
  39. }
  40. if (ratio >= 50) {
  41. return ratio * trackSize.value / 100;
  42. }
  43. const SCROLLBAR_MAX_SIZE = trackSize.value / 3;
  44. return Math.floor(Math.min(Math.max(ratio * trackSize.value / 100, SCROLLBAR_MIN_SIZE), SCROLLBAR_MAX_SIZE));
  45. });
  46. const thumbStyle = computed(() => {
  47. if (!Number.isFinite(thumbSize.value)) {
  48. return {
  49. display: "none"
  50. };
  51. }
  52. const thumb = `${thumbSize.value}px`;
  53. const style = renderThumbStyle({
  54. bar: bar.value,
  55. size: thumb,
  56. move: state.traveled
  57. }, props.layout);
  58. return style;
  59. });
  60. const totalSteps = computed(() => Math.ceil(props.clientSize - thumbSize.value - unref(GAP)));
  61. const attachEvents = () => {
  62. window.addEventListener("mousemove", onMouseMove);
  63. window.addEventListener("mouseup", onMouseUp);
  64. const thumbEl = unref(thumbRef);
  65. if (!thumbEl)
  66. return;
  67. onselectstartStore = document.onselectstart;
  68. document.onselectstart = () => false;
  69. thumbEl.addEventListener("touchmove", onMouseMove, { passive: true });
  70. thumbEl.addEventListener("touchend", onMouseUp);
  71. };
  72. const detachEvents = () => {
  73. window.removeEventListener("mousemove", onMouseMove);
  74. window.removeEventListener("mouseup", onMouseUp);
  75. document.onselectstart = onselectstartStore;
  76. onselectstartStore = null;
  77. const thumbEl = unref(thumbRef);
  78. if (!thumbEl)
  79. return;
  80. thumbEl.removeEventListener("touchmove", onMouseMove);
  81. thumbEl.removeEventListener("touchend", onMouseUp);
  82. };
  83. const onThumbMouseDown = (e) => {
  84. e.stopImmediatePropagation();
  85. if (e.ctrlKey || [1, 2].includes(e.button)) {
  86. return;
  87. }
  88. state.isDragging = true;
  89. state[bar.value.axis] = e.currentTarget[bar.value.offset] - (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]);
  90. emit("start-move");
  91. attachEvents();
  92. };
  93. const onMouseUp = () => {
  94. state.isDragging = false;
  95. state[bar.value.axis] = 0;
  96. emit("stop-move");
  97. detachEvents();
  98. };
  99. const onMouseMove = (e) => {
  100. const { isDragging } = state;
  101. if (!isDragging)
  102. return;
  103. if (!thumbRef.value || !trackRef.value)
  104. return;
  105. const prevPage = state[bar.value.axis];
  106. if (!prevPage)
  107. return;
  108. cAF(frameHandle);
  109. const offset = (trackRef.value.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) * -1;
  110. const thumbClickPosition = thumbRef.value[bar.value.offset] - prevPage;
  111. const distance = offset - thumbClickPosition;
  112. frameHandle = rAF(() => {
  113. state.traveled = Math.max(0, Math.min(distance, totalSteps.value));
  114. emit("scroll", distance, totalSteps.value);
  115. });
  116. };
  117. const clickTrackHandler = (e) => {
  118. const offset = Math.abs(e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]);
  119. const thumbHalf = thumbRef.value[bar.value.offset] / 2;
  120. const distance = offset - thumbHalf;
  121. state.traveled = Math.max(0, Math.min(distance, totalSteps.value));
  122. emit("scroll", distance, totalSteps.value);
  123. };
  124. watch(() => props.scrollFrom, (v) => {
  125. if (state.isDragging)
  126. return;
  127. state.traveled = Math.ceil(v * totalSteps.value);
  128. });
  129. onBeforeUnmount(() => {
  130. detachEvents();
  131. });
  132. return () => {
  133. return h("div", {
  134. role: "presentation",
  135. ref: trackRef,
  136. class: [
  137. nsVirtualScrollbar.b(),
  138. props.class,
  139. (props.alwaysOn || state.isDragging) && "always-on"
  140. ],
  141. style: trackStyle.value,
  142. onMousedown: withModifiers(clickTrackHandler, ["stop", "prevent"]),
  143. onTouchstartPrevent: onThumbMouseDown
  144. }, h("div", {
  145. ref: thumbRef,
  146. class: nsScrollbar.e("thumb"),
  147. style: thumbStyle.value,
  148. onMousedown: onThumbMouseDown
  149. }, []));
  150. };
  151. }
  152. });
  153. export { ScrollBar as default };
  154. //# sourceMappingURL=scrollbar.mjs.map