|
import React from 'react'; |
|
import ReactDOM from 'react-dom'; |
|
import { getPointRelativeToElement, getDistanceBetweenPoints } from 'utilities/geometry'; |
|
|
|
const THRESHOLD = 150; |
|
const RANGE = 80; |
|
const SCROLL_RANGE = 5; |
|
const COMPLETE = 1; |
|
const SPEED = 0.015; |
|
|
|
const pointFromTouch = (touch) => ({ x: touch.clientX, y: touch.clientY }); |
|
|
|
class LongPressGesture extends React.Component { |
|
constructor() { |
|
super(...arguments); |
|
|
|
this.handleTouchStart = this.handleTouchStart.bind(this); |
|
this.handleTouchMove = this.handleTouchMove.bind(this); |
|
this.handleTouchEnd = this.handleTouchEnd.bind(this); |
|
this.handleTick = this.handleTick.bind(this); |
|
|
|
this.startPoint = null; |
|
this.timer = null; |
|
this.progress = 0; |
|
this.startDate = null; |
|
this.isRecognizing = false; |
|
} |
|
|
|
componentDidMount() { |
|
this.el = ReactDOM.findDOMNode(this); |
|
if (this.props.when) { |
|
this.listen(); |
|
} |
|
} |
|
|
|
componentWillUnmount() { |
|
if (this.props.when) { |
|
this.unlisten(); |
|
} |
|
} |
|
|
|
componentWillReceiveProps(next) { |
|
if (this.props.when !== next.when) { |
|
if (next.when) { |
|
this.listen(); |
|
} else { |
|
this.unlisten(); |
|
} |
|
} |
|
} |
|
|
|
listen() { |
|
this.el.addEventListener('touchstart', this.handleTouchStart, false); |
|
this.el.addEventListener('touchmove', this.handleTouchMove, false); |
|
this.el.addEventListener('touchend', this.handleTouchEnd, false); |
|
} |
|
|
|
unlisten() { |
|
this.el.removeEventListener('touchstart', this.handleTouchStart, false); |
|
this.el.removeEventListener('touchmove', this.handleTouchMove, false); |
|
this.el.removeEventListener('touchend', this.handleTouchEnd, false); |
|
} |
|
|
|
handleTouchStart(event) { |
|
if (event.touches.length !== 1) return null; |
|
this.startDate = Date.now(); |
|
this.isTouching = true; |
|
this.startTouchPoint = getPointRelativeToElement(pointFromTouch(event.touches[0]), this.el); |
|
this.startOffsetPoint = { |
|
x: window.scrollX, |
|
y: window.scrollY, |
|
}; |
|
|
|
if (this.lastTouchPoint && getDistanceBetweenPoints(this.startTouchPoint, this.lastTouchPoint) > RANGE) { |
|
this.props.onLongPressCancel(this.lastTouchPoint.x, this.lastTouchPoint.y); |
|
this.reset(); |
|
} |
|
|
|
this.lastTouchPoint = this.startTouchPoint; |
|
|
|
if (!this.timer) { |
|
this.props.onLongPressStart(this.lastTouchPoint.x, this.lastTouchPoint.y); |
|
this.timer = setInterval(this.handleTick, 1000 / 60); |
|
} |
|
|
|
} |
|
|
|
handleTouchMove(event) { |
|
if (event.touches.length !== 1) return null; |
|
this.lastTouchPoint = getPointRelativeToElement(pointFromTouch(event.touches[0]), this.el); |
|
} |
|
|
|
handleTouchEnd(event) { |
|
if (event.touches.length !== 0) return null; |
|
|
|
this.isTouching = false; |
|
} |
|
|
|
handleTick() { |
|
if (!this.isRecognizing && Date.now() > this.startDate + THRESHOLD) return this.isRecognizing = true; |
|
const distance = getDistanceBetweenPoints(this.startTouchPoint, this.lastTouchPoint); |
|
const scrollDistance = getDistanceBetweenPoints(this.startOffsetPoint, { x: window.scrollX, y: window.scrollY}); |
|
if (this.isTouching && distance < RANGE && scrollDistance < SCROLL_RANGE ) { |
|
this.tickUp(); |
|
} else { |
|
this.tickDown(); |
|
} |
|
} |
|
|
|
tickUp() { |
|
if (this.progress >= COMPLETE) { |
|
this.props.onLongPressProgress(this.lastTouchPoint.x, this.lastTouchPoint.y, 1); |
|
this.props.onLongPressEnd(this.lastTouchPoint.x, this.lastTouchPoint.y); |
|
|
|
this.reset(); |
|
} else { |
|
this.progress += SPEED; |
|
this.props.onLongPressProgress(this.lastTouchPoint.x, this.lastTouchPoint.y, this.progress); |
|
} |
|
} |
|
|
|
tickDown() { |
|
if (this.progress <= 0) { |
|
this.props.onLongPressCancel(this.lastTouchPoint.x, this.lastTouchPoint.y); |
|
this.reset(); |
|
} else { |
|
this.progress -= SPEED; |
|
this.props.onLongPressProgress(this.lastTouchPoint.x, this.lastTouchPoint.y, this.progress); |
|
} |
|
} |
|
|
|
reset() { |
|
clearInterval(this.timer); |
|
this.timer = null; |
|
this.progress = 0; |
|
this.isRecognizing = false; |
|
this.startDate = null; |
|
} |
|
|
|
render() { |
|
return React.Children.only(this.props.children); |
|
} |
|
} |
|
|
|
LongPressGesture.propTypes = { |
|
onLongPressStart: React.PropTypes.func.isRequired, |
|
onLongPressProgress: React.PropTypes.func.isRequired, |
|
onLongPressCancel: React.PropTypes.func.isRequired, |
|
onLongPressEnd: React.PropTypes.func.isRequired, |
|
}; |
|
|
|
export default LongPressGesture; |