useTouchMove.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  2. import * as React from 'react';
  3. import { useRef, useState } from 'react';
  4. var MIN_SWIPE_DISTANCE = 0.1;
  5. var STOP_SWIPE_DISTANCE = 0.01;
  6. var REFRESH_INTERVAL = 20;
  7. var SPEED_OFF_MULTIPLE = Math.pow(0.995, REFRESH_INTERVAL);
  8. // ================================= Hook =================================
  9. export default function useTouchMove(ref, onOffset) {
  10. var _useState = useState(),
  11. _useState2 = _slicedToArray(_useState, 2),
  12. touchPosition = _useState2[0],
  13. setTouchPosition = _useState2[1];
  14. var _useState3 = useState(0),
  15. _useState4 = _slicedToArray(_useState3, 2),
  16. lastTimestamp = _useState4[0],
  17. setLastTimestamp = _useState4[1];
  18. var _useState5 = useState(0),
  19. _useState6 = _slicedToArray(_useState5, 2),
  20. lastTimeDiff = _useState6[0],
  21. setLastTimeDiff = _useState6[1];
  22. var _useState7 = useState(),
  23. _useState8 = _slicedToArray(_useState7, 2),
  24. lastOffset = _useState8[0],
  25. setLastOffset = _useState8[1];
  26. var motionRef = useRef();
  27. // ========================= Events =========================
  28. // >>> Touch events
  29. function onTouchStart(e) {
  30. var _e$touches$ = e.touches[0],
  31. screenX = _e$touches$.screenX,
  32. screenY = _e$touches$.screenY;
  33. setTouchPosition({
  34. x: screenX,
  35. y: screenY
  36. });
  37. window.clearInterval(motionRef.current);
  38. }
  39. function onTouchMove(e) {
  40. if (!touchPosition) return;
  41. // e.preventDefault();
  42. var _e$touches$2 = e.touches[0],
  43. screenX = _e$touches$2.screenX,
  44. screenY = _e$touches$2.screenY;
  45. setTouchPosition({
  46. x: screenX,
  47. y: screenY
  48. });
  49. var offsetX = screenX - touchPosition.x;
  50. var offsetY = screenY - touchPosition.y;
  51. onOffset(offsetX, offsetY);
  52. var now = Date.now();
  53. setLastTimestamp(now);
  54. setLastTimeDiff(now - lastTimestamp);
  55. setLastOffset({
  56. x: offsetX,
  57. y: offsetY
  58. });
  59. }
  60. function onTouchEnd() {
  61. if (!touchPosition) return;
  62. setTouchPosition(null);
  63. setLastOffset(null);
  64. // Swipe if needed
  65. if (lastOffset) {
  66. var distanceX = lastOffset.x / lastTimeDiff;
  67. var distanceY = lastOffset.y / lastTimeDiff;
  68. var absX = Math.abs(distanceX);
  69. var absY = Math.abs(distanceY);
  70. // Skip swipe if low distance
  71. if (Math.max(absX, absY) < MIN_SWIPE_DISTANCE) return;
  72. var currentX = distanceX;
  73. var currentY = distanceY;
  74. motionRef.current = window.setInterval(function () {
  75. if (Math.abs(currentX) < STOP_SWIPE_DISTANCE && Math.abs(currentY) < STOP_SWIPE_DISTANCE) {
  76. window.clearInterval(motionRef.current);
  77. return;
  78. }
  79. currentX *= SPEED_OFF_MULTIPLE;
  80. currentY *= SPEED_OFF_MULTIPLE;
  81. onOffset(currentX * REFRESH_INTERVAL, currentY * REFRESH_INTERVAL);
  82. }, REFRESH_INTERVAL);
  83. }
  84. }
  85. // >>> Wheel event
  86. var lastWheelDirectionRef = useRef();
  87. function onWheel(e) {
  88. var deltaX = e.deltaX,
  89. deltaY = e.deltaY;
  90. // Convert both to x & y since wheel only happened on PC
  91. var mixed = 0;
  92. var absX = Math.abs(deltaX);
  93. var absY = Math.abs(deltaY);
  94. if (absX === absY) {
  95. mixed = lastWheelDirectionRef.current === 'x' ? deltaX : deltaY;
  96. } else if (absX > absY) {
  97. mixed = deltaX;
  98. lastWheelDirectionRef.current = 'x';
  99. } else {
  100. mixed = deltaY;
  101. lastWheelDirectionRef.current = 'y';
  102. }
  103. if (onOffset(-mixed, -mixed)) {
  104. e.preventDefault();
  105. }
  106. }
  107. // ========================= Effect =========================
  108. var touchEventsRef = useRef(null);
  109. touchEventsRef.current = {
  110. onTouchStart: onTouchStart,
  111. onTouchMove: onTouchMove,
  112. onTouchEnd: onTouchEnd,
  113. onWheel: onWheel
  114. };
  115. React.useEffect(function () {
  116. function onProxyTouchStart(e) {
  117. touchEventsRef.current.onTouchStart(e);
  118. }
  119. function onProxyTouchMove(e) {
  120. touchEventsRef.current.onTouchMove(e);
  121. }
  122. function onProxyTouchEnd(e) {
  123. touchEventsRef.current.onTouchEnd(e);
  124. }
  125. function onProxyWheel(e) {
  126. touchEventsRef.current.onWheel(e);
  127. }
  128. document.addEventListener('touchmove', onProxyTouchMove, {
  129. passive: false
  130. });
  131. document.addEventListener('touchend', onProxyTouchEnd, {
  132. passive: true
  133. });
  134. // No need to clean up since element removed
  135. ref.current.addEventListener('touchstart', onProxyTouchStart, {
  136. passive: true
  137. });
  138. ref.current.addEventListener('wheel', onProxyWheel, {
  139. passive: false
  140. });
  141. return function () {
  142. document.removeEventListener('touchmove', onProxyTouchMove);
  143. document.removeEventListener('touchend', onProxyTouchEnd);
  144. };
  145. }, []);
  146. }