Skip to content

Instantly share code, notes, and snippets.

@johanneslumpe
Created July 27, 2015 16:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johanneslumpe/7aeb98771f3dd4763d9b to your computer and use it in GitHub Desktop.
Save johanneslumpe/7aeb98771f3dd4763d9b to your computer and use it in GitHub Desktop.
pinch zoom gesture
// utils/calculateMiddlePoint.js
export default (touchA, touchB) => {
const diffX = touchA.pageX - touchB.pageX;
const diffY = touchA.pageY - touchB.pageY;
return Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2));
};
// utils/calculateDistance.js
export default ({x: aX, y: aY}, {x: bX, y: bY}) => {
const x = (aX + bX) / 2;
const y = (aY + bY) / 2;
return {x, y};
};
// pinchZoomable.js
'use strict';
import React, { Component, View } from 'react-native';
import calculateDistance from '../utils/calculateDistance';
import calculateMiddlePoint from '../utils/calculateMiddlePoint';
const makePoint = (touch, scope) => ({
x: touch[`${scope}X`],
y: touch[`${scope}Y`],
});
const initialOrigin = { x: 0, y: 0 };
const initialState = { scale: 1 };
export default ({ min, max }) => BaseComponent => {
return class extends Component {
constructor(props, context) {
super(props, context);
this._lastScale = 1;
this._globalOrigin = initialOrigin;
this._localOrigin = initialOrigin;
this.state = initialState;
}
componentWillReceiveProps(nextProps) {
if (nextProps.resetPinchZoom) {
this._lastScale = 1;
this._globalOrigin = initialOrigin;
this._localOrigin = initialOrigin;
this.setState(initialState);
}
}
onStartShouldSetResponder = ({ nativeEvent: { touches } }) => {
const shouldRespond = touches.length === 2;
if (shouldRespond) {
const [touchA, touchB] = touches;
const aGlobal = makePoint(touchA, 'page');
const aLocal = makePoint(touchA, 'location');
const bGlobal = makePoint(touchA, 'page');
const bLocal = makePoint(touchA, 'location');
this._globalOrigin = calculateMiddlePoint(aGlobal, bGlobal);
this._localOrigin = calculateMiddlePoint(aLocal, bLocal);
this._initialDistance = calculateDistance(touchA, touchB);
}
return shouldRespond;
}
onMoveShouldSetResponder(evt) {
return evt.nativeEvent.touches.length === 2;
}
onResponderGrant = ({ nativeEvent: { touches } }) => {
const { onPinchZoomBegin } = this.props;
onPinchZoomBegin && onPinchZoomBegin(this._localOrigin, this._globalOrigin);
}
onResponderMove = ({ nativeEvent: { touches } }) => {
const { onPinchZoom } = this.props;
const [touchA, touchB] = touches;
const { _lastScale, _initialDistance } = this;
const currentDistance = calculateDistance(touchA, touchB);
let scale = _lastScale * currentDistance / _initialDistance;
scale = scale > max ?
max :
scale < min ?
min :
scale;
this.setState({
scale
});
onPinchZoom && onPinchZoom(scale, this._localOrigin, this._globalOrigin, scale > _lastScale);
}
onResponderTerminationRequest() {
return true;
}
handleTerminationAndRelease = () => {
const { onPinchZoomEnd } = this.props;
const lastScale = this._lastScale;
const nextLastFactor = this._lastScale = this.state.scale;
onPinchZoomEnd && onPinchZoomEnd(nextLastFactor, nextLastFactor > lastScale);
}
render() {
const {
onPinchZoomBegin,
onPinchZoom,
onPinchZoomEnd,
pinchZoomDecoratorStyle,
...props
} = this.props;
const style = {
...pinchZoomDecoratorStyle,
alignSelf: 'flex-start'
};
return (
<View
onStartShouldSetResponder={this.onStartShouldSetResponder}
onMoveShouldSetResponder={this.onMoveShouldSetResponder}
onResponderGrant={this.onResponderGrant}
onResponderMove={this.onResponderMove}
onResponderTerminationRequest={this.onResponderTerminationRequest}
onResponderRelease={this.handleTerminationAndRelease}
onResponderTerminate={this.handleTerminationAndRelease}
style={style}
>
<BaseComponent
ref="decorated"
{...this.props}
{...this.state}
lastScale={this._lastScale}
globalScaleOrigin={this._globalOrigin}
localScaleOrigin={this._localOrigin}
/>
</View>
);
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment