react-tappable
Version:
Touch / Tappable Event Handling Component for React
105 lines (86 loc) • 4.1 kB
JavaScript
'use strict';
var PropTypes = require('prop-types');
var React = require('react');
function getPinchProps(touches) {
return {
touches: Array.prototype.map.call(touches, function copyTouch(touch) {
return { identifier: touch.identifier, pageX: touch.pageX, pageY: touch.pageY };
}),
center: { x: (touches[0].pageX + touches[1].pageX) / 2, y: (touches[0].pageY + touches[1].pageY) / 2 },
angle: Math.atan() * (touches[1].pageY - touches[0].pageY) / (touches[1].pageX - touches[0].pageX) * 180 / Math.PI,
distance: Math.sqrt(Math.pow(Math.abs(touches[1].pageX - touches[0].pageX), 2) + Math.pow(Math.abs(touches[1].pageY - touches[0].pageY), 2))
};
}
var Mixin = {
propTypes: {
onPinchStart: PropTypes.func, // fires when a pinch gesture is started
onPinchMove: PropTypes.func, // fires on every touch-move when a pinch action is active
onPinchEnd: PropTypes.func // fires when a pinch action ends
},
onPinchStart: function onPinchStart(event) {
// in case the two touches didn't start exactly at the same time
if (this._initialTouch) {
this.endTouch();
}
var touches = event.touches;
this._initialPinch = getPinchProps(touches);
this._initialPinch = Object.assign(this._initialPinch, {
displacement: { x: 0, y: 0 },
displacementVelocity: { x: 0, y: 0 },
rotation: 0,
rotationVelocity: 0,
zoom: 1,
zoomVelocity: 0,
time: Date.now()
});
this._lastPinch = this._initialPinch;
this.props.onPinchStart && this.props.onPinchStart(this._initialPinch, event);
},
onPinchMove: function onPinchMove(event) {
if (this._initialTouch) {
this.endTouch();
}
var touches = event.touches;
if (touches.length !== 2) {
return this.onPinchEnd(event); // bail out before disaster
}
var currentPinch = touches[0].identifier === this._initialPinch.touches[0].identifier && touches[1].identifier === this._initialPinch.touches[1].identifier ? getPinchProps(touches) // the touches are in the correct order
: touches[1].identifier === this._initialPinch.touches[0].identifier && touches[0].identifier === this._initialPinch.touches[1].identifier ? getPinchProps(touches.reverse()) // the touches have somehow changed order
: getPinchProps(touches); // something is wrong, but we still have two touch-points, so we try not to fail
currentPinch.displacement = {
x: currentPinch.center.x - this._initialPinch.center.x,
y: currentPinch.center.y - this._initialPinch.center.y
};
currentPinch.time = Date.now();
var timeSinceLastPinch = currentPinch.time - this._lastPinch.time;
currentPinch.displacementVelocity = {
x: (currentPinch.displacement.x - this._lastPinch.displacement.x) / timeSinceLastPinch,
y: (currentPinch.displacement.y - this._lastPinch.displacement.y) / timeSinceLastPinch
};
currentPinch.rotation = currentPinch.angle - this._initialPinch.angle;
currentPinch.rotationVelocity = currentPinch.rotation - this._lastPinch.rotation / timeSinceLastPinch;
currentPinch.zoom = currentPinch.distance / this._initialPinch.distance;
currentPinch.zoomVelocity = (currentPinch.zoom - this._lastPinch.zoom) / timeSinceLastPinch;
this.props.onPinchMove && this.props.onPinchMove(currentPinch, event);
this._lastPinch = currentPinch;
},
onPinchEnd: function onPinchEnd(event) {
// TODO use helper to order touches by identifier and use actual values on touchEnd.
var currentPinch = Object.assign({}, this._lastPinch);
currentPinch.time = Date.now();
if (currentPinch.time - this._lastPinch.time > 16) {
currentPinch.displacementVelocity = 0;
currentPinch.rotationVelocity = 0;
currentPinch.zoomVelocity = 0;
}
this.props.onPinchEnd && this.props.onPinchEnd(currentPinch, event);
this._initialPinch = this._lastPinch = null;
// If one finger is still on screen, it should start a new touch event for swiping etc
// But it should never fire an onTap or onPress event.
// Since there is no support swipes yet, this should be disregarded for now
// if (event.touches.length === 1) {
// this.onTouchStart(event);
// }
}
};
module.exports = Mixin;