Skip to content

Instantly share code, notes, and snippets.

@MrXyfir
Last active February 2, 2021 17:15
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 MrXyfir/3708abbf40ebe49d46719dc6c6c2ff37 to your computer and use it in GitHub Desktop.
Save MrXyfir/3708abbf40ebe49d46719dc6c6c2ff37 to your computer and use it in GitHub Desktop.
Drag / Pinch / Rotate / Scale / Zoom / Pan with React Native and TypeScript via react-native-gesture-handler
import { StyleSheet, ViewStyle, Animated, View } from 'react-native';
import React from 'react';
import {
RotationGestureHandlerStateChangeEvent,
PinchGestureHandlerStateChangeEvent,
PanGestureHandlerStateChangeEvent,
RotationGestureHandler,
State as GestureState,
PinchGestureHandler,
PanGestureHandler,
} from 'react-native-gesture-handler';
export class GestureHandler extends React.Component<{
onTransform(transform: ViewStyle['transform']): void;
children: React.ReactNode;
x: number;
y: number;
z: number;
w: number;
h: number;
}> {
private rotationRef = React.createRef<RotationGestureHandler>();
private pinchScale = new Animated.Value(1);
private translateX = new Animated.Value(0);
private translateY = new Animated.Value(0);
private lastOffset = { x: 0, y: 0 };
private lastRotate = 0;
private baseScale = new Animated.Value(1);
private lastScale = 1;
private pinchRef = React.createRef<PinchGestureHandler>();
private panRef = React.createRef<PanGestureHandler>();
private rotate = new Animated.Value(0);
private rotateStr = this.rotate.interpolate({
outputRange: ['-100rad', '100rad'],
inputRange: [-100, 100],
});
private scale = Animated.multiply(this.baseScale, this.pinchScale);
private onRotateHandlerStateChange = (
event: RotationGestureHandlerStateChangeEvent,
) => {
if (event.nativeEvent.oldState == GestureState.ACTIVE) {
this.lastRotate += event.nativeEvent.rotation;
this.rotate.setOffset(this.lastRotate);
this.rotate.setValue(0);
this.onChange();
}
};
private onPinchHandlerStateChange = (
event: PinchGestureHandlerStateChangeEvent,
) => {
if (event.nativeEvent.oldState == GestureState.ACTIVE) {
this.lastScale *= event.nativeEvent.scale;
this.baseScale.setValue(this.lastScale);
this.pinchScale.setValue(1);
this.onChange();
}
};
private onPanHandlerStateChange = (
event: PanGestureHandlerStateChangeEvent,
) => {
if (event.nativeEvent.oldState == GestureState.ACTIVE) {
this.lastOffset.x += event.nativeEvent.translationX;
this.lastOffset.y += event.nativeEvent.translationY;
this.translateX.setOffset(this.lastOffset.x);
this.translateX.setValue(0);
this.translateY.setOffset(this.lastOffset.y);
this.translateY.setValue(0);
this.onChange();
}
};
private onRotateGestureEvent = Animated.event(
[{ nativeEvent: { rotation: this.rotate } }],
{ useNativeDriver: true },
);
private onPinchGestureEvent = Animated.event(
[{ nativeEvent: { scale: this.pinchScale } }],
{ useNativeDriver: true },
);
private onPanGestureEvent = Animated.event(
[
{
nativeEvent: {
translationX: this.translateX,
translationY: this.translateY,
},
},
],
{ useNativeDriver: true },
);
private onChange(): void {
this.props.onTransform([
{ translateX: this.lastOffset.x },
{ translateY: this.lastOffset.y },
{ rotate: this.lastRotate + 'rad' },
{ scale: this.lastScale },
]);
}
public render(): JSX.Element {
return (
<PanGestureHandler
onHandlerStateChange={this.onPanHandlerStateChange}
simultaneousHandlers={[this.rotationRef, this.pinchRef]}
onGestureEvent={this.onPanGestureEvent}
minPointers={1}
maxPointers={1}
avgTouches
ref={this.panRef}
>
<Animated.View
style={[
styles.wrapper,
{
transform: [
{ translateX: this.translateX },
{ translateY: this.translateY },
],
position: 'absolute',
zIndex: this.props.z,
height: this.props.h,
width: this.props.w,
left: this.props.x,
top: this.props.y,
},
]}
>
<RotationGestureHandler
onHandlerStateChange={this.onRotateHandlerStateChange}
simultaneousHandlers={this.pinchRef}
onGestureEvent={this.onRotateGestureEvent}
ref={this.rotationRef}
>
<Animated.View
style={[
styles.wrapper,
{
transform: [{ rotate: this.rotateStr }],
height: this.props.h,
width: this.props.w,
},
]}
>
<PinchGestureHandler
onHandlerStateChange={this.onPinchHandlerStateChange}
simultaneousHandlers={this.rotationRef}
onGestureEvent={this.onPinchGestureEvent}
ref={this.pinchRef}
>
<Animated.View
collapsable={false}
style={[
styles.container,
{
transform: [{ scale: this.scale }],
height: this.props.h,
width: this.props.w,
},
]}
>
<View
pointerEvents="none"
style={{
height: this.props.h,
width: this.props.w,
}}
>
{this.props.children}
</View>
</Animated.View>
</PinchGestureHandler>
</Animated.View>
</RotationGestureHandler>
</Animated.View>
</PanGestureHandler>
);
}
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
flex: 1,
},
wrapper: {
flex: 1,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment