Skip to main content

Gestures

When integrating with reanimated, we recommend using react-native-gesture-handler.

We've prepared a few tutorials that showcase the use of advanced gestures within the context of Skia drawings.

tsx
import { useWindowDimensions } from "react-native";
import { Canvas, Circle, Fill } from "@shopify/react-native-skia";
import { GestureDetector, Gesture } from "react-native-gesture-handler";
import { useSharedValue, withDecay } from "react-native-reanimated";
 
export const AnimationWithTouchHandler = () => {
const { width } = useWindowDimensions();
const leftBoundary = 0;
const rightBoundary = width;
const translateX = useSharedValue(width / 2);
 
const gesture = Gesture.Pan()
.onChange((e) => {
translateX.value += e.changeX;
})
.onEnd((e) => {
translateX.value = withDecay({
velocity: e.velocityX,
clamp: [leftBoundary, rightBoundary],
});
});
 
return (
<GestureDetector gesture={gesture}>
<Canvas style={{ flex: 1 }}>
<Fill color="white" />
<Circle cx={translateX} cy={40} r={20} color="#3E3E" />
</Canvas>
</GestureDetector>
);
};
tsx
import { useWindowDimensions } from "react-native";
import { Canvas, Circle, Fill } from "@shopify/react-native-skia";
import { GestureDetector, Gesture } from "react-native-gesture-handler";
import { useSharedValue, withDecay } from "react-native-reanimated";
 
export const AnimationWithTouchHandler = () => {
const { width } = useWindowDimensions();
const leftBoundary = 0;
const rightBoundary = width;
const translateX = useSharedValue(width / 2);
 
const gesture = Gesture.Pan()
.onChange((e) => {
translateX.value += e.changeX;
})
.onEnd((e) => {
translateX.value = withDecay({
velocity: e.velocityX,
clamp: [leftBoundary, rightBoundary],
});
});
 
return (
<GestureDetector gesture={gesture}>
<Canvas style={{ flex: 1 }}>
<Fill color="white" />
<Circle cx={translateX} cy={40} r={20} color="#3E3E" />
</Canvas>
</GestureDetector>
);
};

Element Tracking

A common use-case involves activating gestures only for a specific element on the Canvas. The Gesture Handler excels in this area as it can account for all the transformations applied to an element, such as translations, scaling, and rotations. To track each element, overlay an animated view on it, ensuring that the same transformations applied to the canvas element are mirrored on the animated view.

In the example below, each circle is tracked separately by two gesture handlers.

tsx
import { View } from "react-native";
import { Canvas, Circle, Fill } from "@shopify/react-native-skia";
import { GestureDetector, Gesture } from "react-native-gesture-handler";
import Animated, { useSharedValue, useAnimatedStyle } from "react-native-reanimated";
 
const radius = 30;
 
export const ElementTracking = () => {
// The position of the ball
const x = useSharedValue(100);
const y = useSharedValue(100);
// This style will be applied to the "invisible" animated view
// that overlays the ball
const style = useAnimatedStyle(() => ({
position: "absolute",
top: -radius,
left: -radius,
width: radius * 2,
height: radius * 2,
transform: [{ translateX: x.value }, { translateY: y.value }],
}));
// The gesture handler for the ball
const gesture = Gesture.Pan().onChange((e) => {
x.value += e.x;
y.value += e.y;
});
return (
<View style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }}>
<Fill color="white" />
<Circle cx={x} cy={y} r={radius} color="cyan" />
</Canvas>
<GestureDetector gesture={gesture}>
<Animated.View style={style} />
</GestureDetector>
</View>
);
};
tsx
import { View } from "react-native";
import { Canvas, Circle, Fill } from "@shopify/react-native-skia";
import { GestureDetector, Gesture } from "react-native-gesture-handler";
import Animated, { useSharedValue, useAnimatedStyle } from "react-native-reanimated";
 
const radius = 30;
 
export const ElementTracking = () => {
// The position of the ball
const x = useSharedValue(100);
const y = useSharedValue(100);
// This style will be applied to the "invisible" animated view
// that overlays the ball
const style = useAnimatedStyle(() => ({
position: "absolute",
top: -radius,
left: -radius,
width: radius * 2,
height: radius * 2,
transform: [{ translateX: x.value }, { translateY: y.value }],
}));
// The gesture handler for the ball
const gesture = Gesture.Pan().onChange((e) => {
x.value += e.x;
y.value += e.y;
});
return (
<View style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }}>
<Fill color="white" />
<Circle cx={x} cy={y} r={radius} color="cyan" />
</Canvas>
<GestureDetector gesture={gesture}>
<Animated.View style={style} />
</GestureDetector>
</View>
);
};