126 lines
3.9 KiB
JavaScript
126 lines
3.9 KiB
JavaScript
import bindAll from 'lodash.bindall';
|
|
import {getEventXY} from '../lib/touch-utils';
|
|
|
|
class DragRecognizer {
|
|
/* Gesture states */
|
|
static get STATE_UNIDENTIFIED () {
|
|
return 'unidentified';
|
|
}
|
|
static get STATE_SCROLL () {
|
|
return 'scroll';
|
|
}
|
|
static get STATE_DRAG () {
|
|
return 'drag';
|
|
}
|
|
|
|
constructor ({
|
|
onDrag = (() => {}),
|
|
onDragEnd = (() => {}),
|
|
touchDragAngle = 70, // Angle and distance thresholds are the same as scratch-blocks
|
|
distanceThreshold = 3
|
|
}) {
|
|
this._onDrag = onDrag;
|
|
this._onDragEnd = onDragEnd;
|
|
this._touchDragAngle = touchDragAngle;
|
|
this._distanceThreshold = distanceThreshold;
|
|
|
|
this._initialOffset = null;
|
|
this._gestureState = DragRecognizer.STATE_UNIDENTIFIED;
|
|
|
|
bindAll(this, [
|
|
'start',
|
|
'gestureInProgress',
|
|
'reset',
|
|
'_handleMove',
|
|
'_handleEnd'
|
|
]);
|
|
}
|
|
|
|
start (event) {
|
|
this._initialOffset = getEventXY(event);
|
|
this._bindListeners();
|
|
}
|
|
|
|
gestureInProgress () {
|
|
return this._gestureState !== DragRecognizer.STATE_UNIDENTIFIED;
|
|
}
|
|
|
|
reset () {
|
|
this._unbindListeners();
|
|
this._initialOffset = null;
|
|
this._gestureState = DragRecognizer.STATE_UNIDENTIFIED;
|
|
}
|
|
|
|
//
|
|
// Internal functions
|
|
//
|
|
|
|
_bindListeners () {
|
|
window.addEventListener('mouseup', this._handleEnd);
|
|
window.addEventListener('mousemove', this._handleMove);
|
|
window.addEventListener('touchend', this._handleEnd);
|
|
// touchmove must be marked as non-passive, or else it cannot prevent scrolling
|
|
window.addEventListener('touchmove', this._handleMove, {passive: false});
|
|
}
|
|
|
|
_unbindListeners () {
|
|
window.removeEventListener('mouseup', this._handleEnd);
|
|
window.removeEventListener('mousemove', this._handleMove);
|
|
window.removeEventListener('touchend', this._handleEnd);
|
|
window.removeEventListener('touchmove', this._handleMove, {passive: false});
|
|
}
|
|
|
|
_handleMove (event) {
|
|
// For gestures identified as vertical scrolls, do not process movement events
|
|
if (this._isScroll()) return;
|
|
|
|
const currentOffset = getEventXY(event);
|
|
|
|
// Try to identify this gesture if it hasn't been identified already
|
|
if (!this.gestureInProgress()) {
|
|
const dx = currentOffset.x - this._initialOffset.x;
|
|
const dy = currentOffset.y - this._initialOffset.y;
|
|
const dragDistance = Math.sqrt((dx * dx) + (dy * dy));
|
|
if (dragDistance < this._distanceThreshold) return;
|
|
|
|
// For touch moves, additionally check if the angle suggests drag vs. scroll
|
|
if (event.type === 'touchmove') {
|
|
// Direction goes from -180 to 180, with 0 toward the right.
|
|
let angle = Math.atan2(dy, dx) / Math.PI * 180;
|
|
// Fold over horizontal axis, range now 0 to 180
|
|
angle = Math.abs(angle);
|
|
// Fold over vertical axis, range now 0 to 90
|
|
if (angle > 90) angle = 180 - angle;
|
|
if (angle > this._touchDragAngle) {
|
|
this._gestureState = DragRecognizer.STATE_SCROLL;
|
|
} else {
|
|
this._gestureState = DragRecognizer.STATE_DRAG;
|
|
}
|
|
} else {
|
|
// Mouse moves are always considered drags
|
|
this._gestureState = DragRecognizer.STATE_DRAG;
|
|
}
|
|
}
|
|
|
|
if (this._isDrag()) {
|
|
this._onDrag(currentOffset, this._initialOffset);
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
|
|
_handleEnd () {
|
|
this._onDragEnd();
|
|
this.reset();
|
|
}
|
|
|
|
_isDrag () {
|
|
return this._gestureState === DragRecognizer.STATE_DRAG;
|
|
}
|
|
|
|
_isScroll () {
|
|
return this._gestureState === DragRecognizer.STATE_SCROLL;
|
|
}
|
|
}
|
|
|
|
export default DragRecognizer;
|