Skip to content

Instantly share code, notes, and snippets.

@LukaGiorgadze
Last active October 1, 2019 08:10
Show Gist options
  • Save LukaGiorgadze/8352f94a0e7bcf3f20244a588806f93e to your computer and use it in GitHub Desktop.
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,
// @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