I am currently implementing a react-native-svg based drawing input for my Expo application (it is a slight modification of this answer: link). I use a PanResponder
to register move events and create different paths that I then display as multiple Polyline
elements while the user draws on the view like so:
const GesturePath: React.FC<GesturePathProps> = ({
paths,
color,
width,
height,
strokeWidth,
}) => {
return (
<Svg height='100%' width='100%' viewBox={`0 0 ${width} ${height}`}>
{paths.map((path) => (
<Polyline
key={path.id}
points={path.points.map((p) => `${p.x},${p.y}`).join(" ")}
fill='none'
stroke={color}
strokeWidth={strokeWidth}
/>
))}
</Svg>
);
};
Unfortunately, the lines I produce are extremely rough and jagged and I believe the PanResponder.onPanResponderMove
handler is fired too infrequently for my needs (on average it is called every 50ms on the Android Pixel 4 emulator and I'm not sure if I can expect more from a real device).
Maybe there is better candidate than PanResponder for handling gestures in my use case?
I have implemented a smoothing function (based on this answer link) that behaves correctly but since the points are so distant from each other the user's input gets noticeably distorted.
Here's an example without smoothing:

Below my GestureHandler implementation:
const GestureRecorder: React.FC<GestureRecorderProps> = ({ addPath }) => {
const buffRef = useRef<Position[]>([]);
const pathRef = useRef<Position[]>([]);
const timeRef = useRef<number>(Date.now());
const pathIdRef = useRef<number>(0);
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => true,
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderMove: (event) => {
// workaround for release event
// not working consistently on android
if (Date.now() - timeRef.current > RELEASE_TIME) {
pathIdRef.current += 1;
pathRef.current = [];
buffRef.current = [];
}
timeRef.current = Date.now();
pathRef.current.push({
x: event.nativeEvent.locationX,
y: event.nativeEvent.locationY,
});
addPath({
points: calculateSmoothedPath(event),
id: pathIdRef.current,
});
},
// not working on Android
// release event is not consistent
// onPanResponderRelease: () => {
// pathIdRef.current += 1;
// pathRef.current = [];
// buffRef.current = [];
// },
})
).current;
const calculateSmoothedPath = (event: GestureResponderEvent) => {
// implementation
// ...
// see: https://stackoverflow.com/questions/40324313/svg-smooth-freehand-drawing
}
return (
<View
style={StyleSheet.absoluteFill}
collapsable={false}
{...panResponder.panHandlers}
/>
);
};
Side Note
I have not found any documentation on PanResponder suggesting that a sample rate configuration option exists so I am totally open to alternatives (even dropping the PanResponder + native SVG method entirely) as long as I don't have to eject the expo project and I can have control over the layout (I don't want to use external components that come with specific UIs).
I have tried using the expo-pixi library (specifically the Sketch
component), but the repository seems not to be mantained anymore and the expo client crashes consistently when using it.