useDragScroll.ts 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import { useRef, useCallback, MouseEvent } from 'react';
  2. const DRAG_THRESHOLD = 5;
  3. export default function useDragScroll<T extends HTMLElement = HTMLElement>() {
  4. const ref = useRef<T>(null);
  5. const isDragging = useRef(false);
  6. const hasMoved = useRef(false);
  7. const startX = useRef(0);
  8. const scrollLeft = useRef(0);
  9. const onMouseDown = useCallback((e: MouseEvent) => {
  10. const el = ref.current;
  11. if (!el) return;
  12. e.preventDefault();
  13. isDragging.current = true;
  14. hasMoved.current = false;
  15. startX.current = e.pageX - el.offsetLeft;
  16. scrollLeft.current = el.scrollLeft;
  17. el.style.cursor = 'grabbing';
  18. }, []);
  19. const onMouseMove = useCallback((e: MouseEvent) => {
  20. if (!isDragging.current) return;
  21. const el = ref.current;
  22. if (!el) return;
  23. const x = e.pageX - el.offsetLeft;
  24. const walk = x - startX.current;
  25. if (!hasMoved.current && Math.abs(walk) > DRAG_THRESHOLD) {
  26. hasMoved.current = true;
  27. el.style.pointerEvents = 'auto';
  28. for (const child of el.children) {
  29. (child as HTMLElement).style.pointerEvents = 'none';
  30. }
  31. }
  32. e.preventDefault();
  33. el.scrollLeft = scrollLeft.current - walk;
  34. }, []);
  35. const resetState = useCallback(() => {
  36. isDragging.current = false;
  37. const el = ref.current;
  38. if (!el) return;
  39. el.style.cursor = 'grab';
  40. if (hasMoved.current) {
  41. for (const child of el.children) {
  42. (child as HTMLElement).style.pointerEvents = '';
  43. }
  44. }
  45. hasMoved.current = false;
  46. }, []);
  47. const onMouseUp = useCallback(() => resetState(), [resetState]);
  48. const onMouseLeave = useCallback(() => resetState(), [resetState]);
  49. return {
  50. ref,
  51. onMouseDown,
  52. onMouseMove,
  53. onMouseUp,
  54. onMouseLeave,
  55. };
  56. }