useCursorPosition.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import {
  2. useState, useRef, useCallback, useEffect,
  3. } from 'react';
  4. import { fromEvent, Subscription } from 'rxjs';
  5. import {
  6. throttleTime,
  7. } from 'rxjs/operators';
  8. import { getAbsoluteCoordinate } from '../helpers/position';
  9. const defaultTime = 35;
  10. type CursorPosition = {
  11. x: number | null;
  12. y: number | null;
  13. clientX: number | null;
  14. clientY: number | null;
  15. screenX: number | null;
  16. screenY: number | null;
  17. };
  18. type UseCursorPositionType = (
  19. (throttleTime?: number) => [
  20. CursorPosition, (element: HTMLElement| SVGSVGElement | SVGCircleElement | null) => void
  21. ]
  22. );
  23. const initialState = {
  24. x: null,
  25. y: null,
  26. clientX: null,
  27. clientY: null,
  28. screenX: null,
  29. screenY: null,
  30. };
  31. const useCursorPosition: UseCursorPositionType = (time = defaultTime) => {
  32. const [state, setState] = useState<CursorPosition>(initialState);
  33. const [
  34. element, setElement,
  35. ] = useState<HTMLElement | SVGSVGElement | SVGCircleElement | null>(null);
  36. const entered = useRef(false);
  37. const isDown = useRef(false);
  38. const onMouseMoveEvent = useCallback((e: MouseEvent | Event): void => {
  39. if (element) {
  40. const {
  41. clientX,
  42. clientY,
  43. screenX,
  44. screenY,
  45. } = e as MouseEvent;
  46. const coordinate = getAbsoluteCoordinate(element, e as MouseEvent);
  47. setState({
  48. x: coordinate.x,
  49. y: coordinate.y,
  50. clientX,
  51. clientY,
  52. screenX,
  53. screenY,
  54. });
  55. }
  56. }, [element]);
  57. const onTouchMoveEvent = useCallback((e: TouchEvent | Event): void => {
  58. if (element) {
  59. const {
  60. clientX,
  61. clientY,
  62. screenX,
  63. screenY,
  64. } = (e as TouchEvent).touches[0];
  65. const coordinate = getAbsoluteCoordinate(element, e as TouchEvent);
  66. setState({
  67. x: coordinate.x,
  68. y: coordinate.y,
  69. clientX,
  70. clientY,
  71. screenX,
  72. screenY,
  73. });
  74. }
  75. }, [element]);
  76. useEffect(() => {
  77. let mouseSubscription: Subscription | null = null;
  78. let touchSubscription: Subscription | null = null;
  79. const onEnter = (): void => {
  80. entered.current = true;
  81. };
  82. const onLeave = (): void => {
  83. entered.current = false;
  84. setState(initialState);
  85. };
  86. const onDown = (e: any): void => {
  87. entered.current = true;
  88. isDown.current = true;
  89. onMouseMoveEvent(e);
  90. };
  91. const onTouch = (e: any): void => {
  92. entered.current = true;
  93. isDown.current = true;
  94. onTouchMoveEvent(e);
  95. };
  96. const onUp = (): void => {
  97. entered.current = false;
  98. isDown.current = false;
  99. setState(initialState);
  100. if (mouseSubscription) mouseSubscription.unsubscribe();
  101. if (touchSubscription) touchSubscription.unsubscribe();
  102. };
  103. if (element) {
  104. mouseSubscription = fromEvent(element, 'mousemove').pipe(throttleTime(time)).subscribe(onMouseMoveEvent);
  105. touchSubscription = fromEvent(element, 'touchmove').pipe(throttleTime(time)).subscribe(onTouchMoveEvent);
  106. const addEvent = element.addEventListener.bind(element);
  107. addEvent('mouseenter', onEnter);
  108. addEvent('mouseleave', onLeave);
  109. addEvent('mousedown', onDown);
  110. addEvent('touchstart', onTouch);
  111. addEvent('mouseup', onUp);
  112. addEvent('touchend', onUp);
  113. }
  114. return (): void => {
  115. if (element) {
  116. const removeEvent = element.removeEventListener.bind(element);
  117. removeEvent('mouseenter', onEnter);
  118. removeEvent('mouseleave', onLeave);
  119. removeEvent('mousedown', onDown);
  120. removeEvent('touchstart', onTouch);
  121. removeEvent('mouseup', onUp);
  122. removeEvent('touchend', onUp);
  123. }
  124. };
  125. }, [element]);
  126. return [state, setElement];
  127. };
  128. export default useCursorPosition;