You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

useZoomEvents.ts 2.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import React, { useEffect, useRef } from 'react'
  2. import state from 'state'
  3. import inputs from 'state/inputs'
  4. import * as vec from 'utils/vec'
  5. import { usePinch } from 'react-use-gesture'
  6. /**
  7. * Capture zoom gestures (pinches, wheels and pans) and send to the state.
  8. * @param ref
  9. * @returns
  10. */
  11. export default function useZoomEvents(
  12. ref: React.MutableRefObject<SVGSVGElement>
  13. ) {
  14. const rTouchDist = useRef(0)
  15. useEffect(() => {
  16. const element = ref.current
  17. if (!element) return
  18. function handleWheel(e: WheelEvent) {
  19. e.preventDefault()
  20. e.stopPropagation()
  21. if (e.ctrlKey) {
  22. state.send('ZOOMED_CAMERA', {
  23. delta: e.deltaY,
  24. ...inputs.wheel(e),
  25. })
  26. return
  27. }
  28. state.send('PANNED_CAMERA', {
  29. delta: [e.deltaX, e.deltaY],
  30. ...inputs.wheel(e),
  31. })
  32. }
  33. function handleTouchMove(e: TouchEvent) {
  34. e.preventDefault()
  35. e.stopPropagation()
  36. if (e.touches.length === 2) {
  37. const { clientX: x0, clientY: y0 } = e.touches[0]
  38. const { clientX: x1, clientY: y1 } = e.touches[1]
  39. const dist = vec.dist([x0, y0], [x1, y1])
  40. const point = vec.med([x0, y0], [x1, y1])
  41. state.send('WHEELED', {
  42. delta: dist - rTouchDist.current,
  43. point,
  44. })
  45. rTouchDist.current = dist
  46. }
  47. }
  48. element.addEventListener('wheel', handleWheel, { passive: false })
  49. element.addEventListener('touchstart', handleTouchMove, { passive: false })
  50. element.addEventListener('touchmove', handleTouchMove, { passive: false })
  51. return () => {
  52. element.removeEventListener('wheel', handleWheel)
  53. element.removeEventListener('touchstart', handleTouchMove)
  54. element.removeEventListener('touchmove', handleTouchMove)
  55. }
  56. }, [ref])
  57. const rPinchDa = useRef<number[] | undefined>(undefined)
  58. const rPinchPoint = useRef<number[] | undefined>(undefined)
  59. const bind = usePinch(({ pinching, da, origin }) => {
  60. if (!pinching) {
  61. state.send('STOPPED_PINCHING')
  62. rPinchDa.current = undefined
  63. rPinchPoint.current = undefined
  64. return
  65. }
  66. if (rPinchPoint.current === undefined) {
  67. state.send('STARTED_PINCHING')
  68. rPinchDa.current = da
  69. rPinchPoint.current = origin
  70. }
  71. const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da)
  72. state.send('PINCHED', {
  73. delta: vec.sub(rPinchPoint.current, origin),
  74. point: origin,
  75. distanceDelta,
  76. angleDelta,
  77. })
  78. rPinchDa.current = da
  79. rPinchPoint.current = origin
  80. })
  81. return { ...bind() }
  82. }