Last active
October 1, 2019 08:10
-
-
Save LukaGiorgadze/8352f94a0e7bcf3f20244a588806f93e to your computer and use it in GitHub Desktop.
This react hook allows you to detect dragging and swiping of an element,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
import { useState, useLayoutEffect } from 'react'; | |
import type { ElementRef } from 'react'; | |
/** | |
* This react hook allows you to detect dragging and swiping | |
* @param ref HTML Element ref (react ref) | |
* @param handler Function Callback | |
* @param config Object | |
* @return Function remove listeners | |
* TODO: Realtime tracking | |
* */ | |
export function useGestures( | |
ref: ElementRef<Object>, | |
handler: Function, | |
config: { realTime: boolean, delay: number, axis: 'x' | 'y' | 'both' }, | |
): Object { | |
const [startX, setStartX] = useState(0); | |
const [endX, setEndX] = useState(0); | |
const [startY, setStartY] = useState(0); | |
const [endY, setEndY] = useState(0); | |
const [currentX, setCurrentX] = useState(0); | |
const [currentY, setCurrentY] = useState(0); | |
const [isMoving, setIsMoving] = useState(false); | |
const [throttled, setThrottled] = useState(false); | |
const hasMouseEvent = 'onmousedown' in document; | |
const hasTouchEvent = 'ontouchstart' in document; | |
const cfg = { | |
realTime: (config && config.realTime) || false, // realtime dragging callback | |
delay: config && config.delay >= 0 && config.delay < 10000 ? config.delay : 100, // delay between calls in realtime dragging | |
axis: config && ['x', 'y', 'both'].includes(config.axis) ? config.axis : 'both', | |
}; | |
const onDragStart = e => { | |
if (e.cancelable) e.preventDefault(); | |
if (cfg.axis === 'x' || cfg.axis === 'both') | |
setStartX(parseInt((e.changedTouches && e.changedTouches[0].screenX) || e.screenX, 10)); | |
if (cfg.axis === 'y' || cfg.axis === 'both') | |
setStartY(parseInt((e.changedTouches && e.changedTouches[0].screenY) || e.screenY, 10)); | |
}; | |
const onDrag = e => { | |
if (e.cancelable) e.preventDefault(); | |
if (cfg.realTime) { | |
if (!throttled) { | |
// actual callback action | |
if (cfg.axis === 'x' || cfg.axis === 'both') | |
setCurrentX(parseInt((e.changedTouches && e.changedTouches[0].screenX) || e.screenX, 10)); | |
if (cfg.axis === 'y' || cfg.axis === 'both') | |
setCurrentY(parseInt((e.changedTouches && e.changedTouches[0].screenY) || e.screenY, 10)); | |
// we're throttled! | |
setThrottled(true); | |
// set a timeout to un-throttle | |
setTimeout(() => { | |
setThrottled(false); | |
}, cfg.delay); | |
} | |
} | |
setIsMoving(true); | |
}; | |
const onDragEnd = e => { | |
if (e.cancelable) e.preventDefault(); | |
if (cfg.axis === 'x' || cfg.axis === 'both') | |
setEndX(parseInt((e.changedTouches && e.changedTouches[0].screenX) || e.screenX, 10)); | |
if (cfg.axis === 'y' || cfg.axis === 'both') | |
setEndY(parseInt((e.changedTouches && e.changedTouches[0].screenY) || e.screenY, 10)); | |
setIsMoving(false); | |
}; | |
const onMouseLeave = e => { | |
if (e.cancelable) e.preventDefault(); | |
setIsMoving(false); | |
}; | |
const addListeners = gesuredZone => { | |
if (hasMouseEvent) { | |
gesuredZone.addEventListener('mousedown', onDragStart, false); | |
gesuredZone.addEventListener('mousemove', onDrag); | |
gesuredZone.addEventListener('mouseup', onDragEnd); | |
gesuredZone.addEventListener('mouseleave', onMouseLeave); | |
} | |
if (hasTouchEvent) { | |
gesuredZone.addEventListener('touchstart', onDragStart, false); | |
gesuredZone.addEventListener('touchmove', onDrag); | |
gesuredZone.addEventListener('touchend', onDragEnd); | |
} | |
}; | |
const removeListeners = gesuredZone => { | |
if (hasMouseEvent) { | |
gesuredZone.removeEventListener('mousedown', onDragStart); | |
gesuredZone.removeEventListener('mousemove', onDrag); | |
gesuredZone.removeEventListener('mouseup', onDragEnd); | |
gesuredZone.removeEventListener('mouseleave', onMouseLeave); | |
} | |
if (hasTouchEvent) { | |
gesuredZone.removeEventListener('touchstart', onDragStart); | |
gesuredZone.removeEventListener('touchmove', onDrag); | |
gesuredZone.removeEventListener('touchend', onDragEnd); | |
} | |
}; | |
useEffect(() => { | |
addListeners(ref.current); | |
// TODO: Direction on real time | |
let direction = 'same'; | |
if (cfg.realTime) { | |
if (currentX > startX) direction = 'right'; | |
if (currentX < startX) direction = 'left'; | |
} else { | |
if (startX > endX) direction = 'left'; | |
if (startX < endX) direction = 'right'; | |
} | |
let res = { isMoving, direction }; | |
if (cfg.axis === 'x' || cfg.axis === 'both') res = { startX, endX, ...res }; | |
if (cfg.axis === 'y' || cfg.axis === 'both') res = { startY, endY, ...res }; | |
if (cfg.realTime && (cfg.axis === 'x' || cfg.axis === 'both')) res = { currentX, ...res }; | |
if (cfg.realTime && (cfg.axis === 'y' || cfg.axis === 'both')) res = { currentY, ...res }; | |
if (startX || startY || endX || endY) handler(res); | |
return () => removeListeners(ref.current); | |
}, [endX, endY, throttled]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment