React Native Academy · บทเรียน

ท่าลากและปัดด้วย Gesture Handler

ติดตั้ง React Native Gesture Handler สร้างการ์ดที่ลากได้ด้วย PanGestureHandler และ useAnimatedGestureHandler แล้วดีดการ์ดกลับไปยังจุดเริ่มต้นเมื่อปล่อย

บทเรียน 3 จาก 413 ขั้นตอน

ท่าลากและปัดด้วย Gesture Handler เป็นบทเรียน React Native Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน React Native Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส React Native Academy มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

What Is React Native Gesture Handler?

React Native Gesture Handler replaces the built-in touch system with one that runs entirely on the native UI thread. The default React Native responder system processes touch events on the JavaScript thread, introducing latency when JS is busy. Gesture Handler moves recognition and response to native code, enabling the same snappy feel as native iOS and Android apps.

Gesture Handler is the standard companion to Reanimated — they are designed to work together. It provides pan, pinch, rotation, tap, long-press, and fling recognizers that feed data directly into Reanimated worklets.

// Install:
// npx expo install react-native-gesture-handler

// Wrap your app root with GestureHandlerRootView:
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <YourApp />
    </GestureHandlerRootView>
  );
}

The Gesture API (v2)

Gesture Handler v2 (the modern API) uses the Gesture factory object to create gestures and the GestureDetector component to attach them to UI. This replaces the older component-based API (PanGestureHandler, TapGestureHandler) with a cleaner compositional model.

You create a gesture object (e.g., Gesture.Pan()), configure it with method chaining, and wrap your component in GestureDetector. The gesture callbacks are worklets that run on the UI thread.

import { Gesture, GestureDetector } from 'react-native-gesture-handler';

const panGesture = Gesture.Pan()
  .onUpdate((event) => {
    'worklet';
    // event.translationX, event.translationY available
  });

return (
  <GestureDetector gesture={panGesture}>
    <Animated.View style={animatedStyle} />
  </GestureDetector>
);

Building a Draggable Card

The simplest pan gesture example is a draggable card. Create shared values for x and y position. In onUpdate, add the gesture's translationX/translationY to a stored start position. In onEnd, save the current position as the new start so the next drag continues from where the card stopped.

The card visually follows the finger without any JavaScript thread involvement — completely smooth even under JS load.

import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';

const offsetX = useSharedValue(0);
const offsetY = useSharedValue(0);
const startX = useSharedValue(0);
const startY = useSharedValue(0);

const pan = Gesture.Pan()
  .onBegin(() => {
    startX.value = offsetX.value;
    startY.value = offsetY.value;
  })
  .onUpdate((e) => {
    offsetX.value = startX.value + e.translationX;
    offsetY.value = startY.value + e.translationY;
  });

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

Snapping Back to Origin on Release

A common pattern is to let the user drag a card freely but snap it back to its original position when released. In the onFinalize or onEnd callback, assign a withSpring animation to the offset values, returning them to zero with a satisfying bounce.

The spring's initial velocity can be set from the gesture's final velocity (event.velocityX, event.velocityY) to create a natural-feeling throw-and-catch effect that continues the motion before bouncing back.

import { withSpring } from 'react-native-reanimated';

const pan = Gesture.Pan()
  .onUpdate((e) => {
    offsetX.value = startX.value + e.translationX;
    offsetY.value = startY.value + e.translationY;
  })
  .onEnd((e) => {
    offsetX.value = withSpring(0, { velocity: e.velocityX });
    offsetY.value = withSpring(0, { velocity: e.velocityY });
  });

Swipe to Dismiss Pattern

A swipe-to-dismiss card tracks horizontal position during the pan, and when the card is released with sufficient horizontal velocity or displacement, it animates off screen with withTiming or withDecay instead of snapping back.

Use event.velocityX to determine if the throw was intentional (fast enough), and event.translationX to check if the card was dragged far enough. If either threshold is met, fly the card off screen and call runOnJS to notify React to remove it.

const DISMISS_VELOCITY = 800;
const DISMISS_DISTANCE = 150;

const pan = Gesture.Pan()
  .onUpdate((e) => { offsetX.value = e.translationX; })
  .onEnd((e) => {
    const shouldDismiss =
      Math.abs(e.velocityX) > DISMISS_VELOCITY ||
      Math.abs(e.translationX) > DISMISS_DISTANCE;

    if (shouldDismiss) {
      const direction = e.translationX > 0 ? 1 : -1;
      offsetX.value = withTiming(direction * 400, { duration: 200 },
        () => runOnJS(onDismiss)()
      );
    } else {
      offsetX.value = withSpring(0);
    }
  });

Tap Gesture for Press Interactions

Gesture.Tap() recognizes taps. Unlike Pressable or TouchableOpacity, a Gesture Handler tap runs its callback on the UI thread, enabling animated feedback without JS involvement. You can configure maxDuration and numberOfTaps for double-tap detection.

Gesture taps are especially useful when combined with Reanimated: the tap callback can directly trigger a spring animation on scale or opacity, giving zero-latency visual feedback that feels instantaneous.

const scale = useSharedValue(1);

const tap = Gesture.Tap()
  .onBegin(() => {
    scale.value = withSpring(0.95);
  })
  .onFinalize(() => {
    scale.value = withSpring(1);
  });

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

return (
  <GestureDetector gesture={tap}>
    <Animated.View style={[styles.button, style]} />
  </GestureDetector>
);

Composing Gestures: Simultaneous and Exclusive

Gesture Handler allows composing multiple gestures on the same component using Gesture.Simultaneous (both run at the same time) and Gesture.Exclusive (only one can be active at a time). This enables complex interactions like swipe-while-zooming on a photo viewer.

Use Gesture.Simultaneous(panGesture, pinchGesture) to allow both pan and pinch on the same element simultaneously. Use Gesture.Exclusive(swipeGesture, tapGesture) to ensure a swipe cancels a tap recognition.

const pan = Gesture.Pan().onUpdate(...);
const pinch = Gesture.Pinch().onUpdate(...);

// Both work at the same time (e.g. photo viewer):
const composedGesture = Gesture.Simultaneous(pan, pinch);

// Only one wins (e.g. swipe OR tap):
const exclusiveGesture = Gesture.Exclusive(
  Gesture.Fling().direction(Directions.LEFT).onEnd(onSwipe),
  Gesture.Tap().onEnd(onTap)
);

Gesture Lifecycle: Begin, Update, End, Finalize

Every gesture recognizer has lifecycle callbacks: onBegin fires when the gesture is recognized, onUpdate fires on every new touch event, onEnd fires when the gesture completes successfully, and onFinalize fires after end or when the gesture is cancelled.

Use onBegin to store the starting position, onUpdate to track motion, onEnd to handle successful completion, and onFinalize to clean up regardless of whether the gesture succeeded or was cancelled (e.g., interrupted by a phone call).

const pan = Gesture.Pan()
  .onBegin(() => { startPos.value = offsetX.value; })
  .onUpdate((e) => { offsetX.value = startPos.value + e.translationX; })
  .onEnd((e) => {
    // Gesture completed successfully
    if (Math.abs(e.translationX) > 100) runOnJS(onSwipeAway)();
    else offsetX.value = withSpring(0);
  })
  .onFinalize(() => {
    // Always runs — reset any temporary state
  });

Long Press Gesture

Gesture.LongPress() recognizes holds that exceed a minimum duration. Configure .minDuration(ms) to set the hold time. A common pattern is triggering a haptic feedback and entering an edit mode after a long press, like reordering items in a list.

Combine long press with pan for drag-to-reorder: the long press activates drag mode, and the pan moves the item while in drag mode. Gesture.Exclusive ensures a tap is ignored while dragging is active.

import * as Haptics from 'expo-haptics';

const [isDragging, setIsDragging] = React.useState(false);

const longPress = Gesture.LongPress()
  .minDuration(400)
  .onStart(() => {
    scale.value = withSpring(1.05);
    runOnJS(Haptics.selectionAsync)();
    runOnJS(setIsDragging)(true);
  });

const pan = Gesture.Pan()
  .enabled(isDragging)
  .onUpdate((e) => {
    offsetY.value = e.translationY;
  });

Gesture State and Enabling/Disabling

Gestures can be conditionally enabled with .enabled(boolean). When disabled, the gesture won't activate, allowing you to toggle interactivity based on app state. You can also call .shouldCancelWhenOutside(true) to cancel a gesture when the finger moves outside the component bounds.

The .hitSlop option expands the touch target area beyond the component's visual bounds — useful for small icon buttons that are hard to tap accurately on mobile screens.

const [isEnabled, setIsEnabled] = React.useState(true);

const tap = Gesture.Tap()
  .enabled(isEnabled)
  .hitSlop({ top: 10, bottom: 10, left: 10, right: 10 })
  .shouldCancelWhenOutside(true)
  .onEnd(() => {
    runOnJS(handleTap)();
  });

// Disable temporarily:
setIsEnabled(false); // tap won't fire

Handling Gesture Conflicts with ScrollView

A common challenge is a draggable card inside a ScrollView — both compete for the same touch event. Gesture Handler provides simultaneousWithExternalGesture and blocksExternalGesture to control priority. The waitFor option delays a gesture until another fails, enabling nested scroll-inside-swipe patterns.

For native scroll views, wrap them with ScrollView from Gesture Handler (import { ScrollView } from 'react-native-gesture-handler') instead of React Native's default to ensure gesture coordination works correctly.

import { ScrollView } from 'react-native-gesture-handler';

// Use GH's ScrollView to allow gesture coordination:
<ScrollView>
  <GestureDetector gesture={pan}>
    <Animated.View style={style}>
      {/* Draggable card inside scroll */}
    </Animated.View>
  </GestureDetector>
</ScrollView>

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: Gesture.Pan() creates a pan gesture recognizer that tracks finger translation in onUpdate and finishes in onEnd, withSpring on offset values creates snap-back or snap-away effects after release, and Gesture.Simultaneous composites multiple gestures so they operate at the same time. Next up we combine PinchGestureHandler and rotation for a zoomable image component.

เริ่มต้นได้ฟรี

เรียนรู้ JavaScript ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
30
บทเรียน
120

คำถามที่พบบ่อย

บทเรียน “ท่าลากและปัดด้วย Gesture Handler” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ท่าลากและปัดด้วย Gesture Handler” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส React Native Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส React Native Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ท่าลากและปัดด้วย Gesture Handler”

ติดตั้ง React Native Gesture Handler สร้างการ์ดที่ลากได้ด้วย PanGestureHandler และ useAnimatedGestureHandler แล้วดีดการ์ดกลับไปยังจุดเริ่มต้นเมื่อปล่อย คุณปฏิบัติ React Native Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน React Native Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน React Native Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “ท่าลากและปัดด้วย Gesture Handler” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน React Native Academy นี้ได้ไหม

ได้ บทเรียน React Native Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การติดตั้ง Reanimated และค่าที่ใช้ร่วมกัน
  2. เวิร์กเล็ตและการรันโค้ดบนเธรด UI
  3. ท่าลากและปัดด้วย Gesture Handler
  4. การซูมด้วยการบีบนิ้วและการหมุน
← กลับไปที่ React Native Academy