Spaces:
Runtime error
Runtime error
| 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) { | |
| if (typeof event.button === 'number' && event.button !== 0) { | |
| return; | |
| } | |
| 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.reset(); | |
| // Call the callback after reset to make sure if gestureInProgress() | |
| // is used in response, it get the correct value (i.e. no gesture in progress) | |
| this._onDragEnd(); | |
| } | |
| _isDrag () { | |
| return this._gestureState === DragRecognizer.STATE_DRAG; | |
| } | |
| _isScroll () { | |
| return this._gestureState === DragRecognizer.STATE_SCROLL; | |
| } | |
| } | |
| export default DragRecognizer; | |