iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Gestures / Reanimated

react-native-gesture-handler runs gesture recognition on the native UI thread, giving you 60fps interaction even when the JS thread is busy. Pair it with react-native-reanimated for shared values and worklets so animations stay native too. Use it for drawer swipes, pull-to-refresh, swipe-to-delete, and any custom gesture.

Pan, pinch, long press, and swipe-to-delete

EXAMPLE
// npm i react-native-gesture-handler react-native-reanimated
// Wrap your app root in <GestureHandlerRootView>.

import React from 'react';
import { GestureHandlerRootView, GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, {
  useSharedValue, useAnimatedStyle, withSpring, withTiming, runOnJS,
} from 'react-native-reanimated';
import { View, Text, StyleSheet, Dimensions } from 'react-native';

const { width } = Dimensions.get('window');

// 1) Pan — drag a card around the screen
export function DraggableCard() {
  const tx = useSharedValue(0);
  const ty = useSharedValue(0);

  const pan = Gesture.Pan()
    .onChange((e) => {
      tx.value += e.changeX;
      ty.value += e.changeY;
    })
    .onEnd(() => {
      tx.value = withSpring(0);
      ty.value = withSpring(0);
    });

  const style = useAnimatedStyle(() => ({
    transform: [{ translateX: tx.value }, { translateY: ty.value }],
  }));

  return (
    <GestureDetector gesture={pan}>
      <Animated.View style={[styles.card, style]}><Text>Drag me</Text></Animated.View>
    </GestureDetector>
  );
}

// 2) Pinch + rotate — composed gestures
export function PinchableImage({ source }) {
  const scale    = useSharedValue(1);
  const rotation = useSharedValue(0);

  const pinch  = Gesture.Pinch().onChange((e) => { scale.value *= 1 + (e.scaleChange - 1); });
  const rotate = Gesture.Rotation().onChange((e) => { rotation.value += e.rotationChange; });
  const both   = Gesture.Simultaneous(pinch, rotate);

  const style = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }, { rotateZ: \`${rotation.value}rad\` }],
  }));
  return (
    <GestureDetector gesture={both}>
      <Animated.Image source={source} style={[styles.image, style]} />
    </GestureDetector>
  );
}

// 3) Swipe-to-delete row
export function SwipeRow({ label, onDelete }) {
  const tx = useSharedValue(0);

  const swipe = Gesture.Pan()
    .activeOffsetX([-10, 10])
    .onChange((e) => { tx.value = Math.min(0, tx.value + e.changeX); })
    .onEnd(() => {
      if (tx.value < -width * 0.4) {
        tx.value = withTiming(-width, { duration: 180 }, (done) => {
          if (done) runOnJS(onDelete)();
        });
      } else {
        tx.value = withSpring(0);
      }
    });

  const style = useAnimatedStyle(() => ({ transform: [{ translateX: tx.value }] }));

  return (
    <GestureDetector gesture={swipe}>
      <Animated.View style={[styles.row, style]}>
        <Text>{label}</Text>
      </Animated.View>
    </GestureDetector>
  );
}

// 4) Long press to enter selection mode
export function LongPressable({ onSelect, children }) {
  const longPress = Gesture.LongPress()
    .minDuration(400)
    .onStart(() => runOnJS(onSelect)());
  return <GestureDetector gesture={longPress}>{children}</GestureDetector>;
}

const styles = StyleSheet.create({
  card:  { width: 180, height: 100, backgroundColor: '#dbeafe', borderRadius: 12,
           alignItems: 'center', justifyContent: 'center' },
  image: { width: 240, height: 240, alignSelf: 'center' },
  row:   { padding: 16, backgroundColor: 'white', borderBottomWidth: 1, borderColor: '#eee' },
});

Why it matters

Anchor the gesture root with GestureHandlerRootView at the top of your tree, otherwise gestures inside modals and overlays silently fail. The rule of thumb: every screen that uses gestures should be a child of one root, never multiple.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
const tap = Gesture.Tap().onEnd(() => console.log('tapped'));
<GestureDetector gesture={tap}>
    <View style={{ padding: 32 }}><Text>Tap me</Text></View>
</GestureDetector>
Try it Yourself »

Discussion

Loading…