はじめに
React Nativeで標準のPanResponderやonTouchStartを使ってスワイプやドラッグを実装したことがある方なら、あの微妙な「引っかかり」を経験したことがあるはずです。タップしたつもりがスクロールとして認識されたり、複数の指の動きが競合してどちらのジェスチャーも中途半端に終わったり。JavaScriptスレッドでタッチイベントを処理している以上、他の処理が重くなればジェスチャーの追従も遅れてしまいます。
React Native Gesture Handlerは、この問題をタッチ処理そのものをネイティブ側に任せることで解決するライブラリです。Software Mansion社が開発・保守しており、GitHubスター数は6,700以上、月間ダウンロード数は2,300万回を超える、React Nativeエコシステムでも中核的な位置づけのパッケージです。React Navigationの内部実装やDrawer、BottomSheet系のライブラリからも依存されており、実質的にデファクトスタンダードといっていいでしょう。
React Native Gesture Handlerとは
React Native Gesture Handlerは、プラットフォームネイティブのタッチ・ジェスチャーシステムを、宣言的なAPIとしてReact Nativeに公開するライブラリです。公式の説明でも "Declarative API exposing native platform touch and gesture system to React Native" と位置づけられており、単なるタッチイベントのラッパーではなく、iOS・AndroidそれぞれのUIKit/Viewレベルのジェスチャー認識機構を直接利用します。
主な特徴
- UIスレッドでの認識 - タッチストリームの処理をUIスレッド側で行うため、JavaScriptスレッドが重い処理でブロックされていてもジェスチャーの追従が途切れません
- 宣言的なGesture API -
Gesture.Pan()やGesture.Tap()のようにジェスチャーの種類ごとにビルダーを組み立て、.onStart()や.onUpdate()でハンドラをチェーンできます - ジェスチャーの合成 -
Gesture.Simultaneous()やGesture.Race()を使って、複数のジェスチャーを「同時に許可する」「早い者勝ちにする」といった関係性ごと表現できます - Reanimatedとの高い親和性 - React Native Reanimatedと組み合わせることで、ジェスチャーの値をそのままアニメーションのSharedValueに流し込み、120fps級のなめらかな動きを実現できます
- 標準コンポーネントの提供 -
SwipeableやDrawerLayoutなど、よく使われるインタラクションパターンがコンポーネントとして用意されています
インストール方法
npmまたはyarnでパッケージを追加します。
npm install react-native-gesture-handler
# または
yarn add react-native-gesture-handler
Expoプロジェクトの場合は、対応バージョンを自動で解決してくれるexpo installを使うのがおすすめです。
npx expo install react-native-gesture-handler
iOSではネイティブモジュールを含むため、Podのインストールが必要です。
cd ios && pod install && cd ..
インストール後、アプリのルートをGestureHandlerRootViewで囲みます。これを忘れるとジェスチャーが正しく認識されないので、App.tsxの一番外側に置いておきましょう。
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
{/* アプリ本体 */}
</GestureHandlerRootView>
);
}
なお、2026年8月時点での最新版はv3.2.1で、React Native 0.82以上が必須になっています。既存プロジェクトでReact Nativeのバージョンが古い場合は、幅広いバージョンに対応するv2系(最新は2.32.0)を使う選択肢もあります。
基本的な使い方
React Native Gesture Handlerの中心にあるのが、GestureビルダーとGestureDetectorコンポーネントです。ジェスチャーの種類ごとにビルダーを作り、イベントハンドラをメソッドチェーンで登録します。
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { View, Text } from 'react-native';
function DraggableBox() {
const panGesture = Gesture.Pan()
.onStart(() => {
console.log('ドラッグ開始');
})
.onUpdate((event) => {
console.log(event.translationX, event.translationY);
})
.onEnd(() => {
console.log('ドラッグ終了');
});
return (
<GestureDetector gesture={panGesture}>
<View style={{ width: 100, height: 100, backgroundColor: 'tomato' }}>
<Text>Drag me</Text>
</View>
</GestureDetector>
);
}
GestureDetectorは子要素を1つだけ受け取り、そこにgestureプロパティで渡したジェスチャーを結びつけます。Gesture.Pan()のほかにも、タップにはGesture.Tap()、ピンチにはGesture.Pinch()、長押しにはGesture.LongPress()、回転にはGesture.Rotation()が用意されており、どれも同じチェーン形式のAPIで扱えます。
実践的なユースケース
スワイプで削除するリストアイテム
リストのアイテムを左右にスワイプして削除する、TodoアプリやメールアプリでおなじみのUIです。Gesture.Pan()で指の移動量(translationX)を追跡し、一定距離を超えたら削除処理をrunOnJSでJS側に戻して呼び出します。
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
runOnJS,
} from 'react-native-reanimated';
function SwipeableItem({ onDelete }: { onDelete: () => void }) {
const translateX = useSharedValue(0);
const pan = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
})
.onEnd(() => {
if (translateX.value < -120) {
runOnJS(onDelete)();
} else {
translateX.value = withSpring(0);
}
});
const style = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<GestureDetector gesture={pan}>
<Animated.View style={style}>{/* アイテム内容 */}</Animated.View>
</GestureDetector>
);
}
onUpdateはジェスチャーの最中に高頻度で呼ばれるため、ここでJavaScript側の関数を直接呼ぶと処理が詰まってしまいます。SharedValueへの代入だけで完結させ、onDeleteのようなJS側の副作用はonEndの中でrunOnJSを使って呼ぶのがポイントです。
ピンチでズームする画像ビューア
写真アプリでおなじみの、指でつまんで画像を拡大縮小する操作です。Gesture.Pinch()が返すscaleイベントプロパティをそのままアニメーションの拡大率に流し込みます。
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
function ZoomableImage() {
const scale = useSharedValue(1);
const savedScale = useSharedValue(1);
const pinch = Gesture.Pinch()
.onUpdate((e) => {
scale.value = savedScale.value * e.scale;
})
.onEnd(() => {
savedScale.value = scale.value;
});
const style = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<GestureDetector gesture={pinch}>
<Animated.View style={style}>{/* 画像コンポーネント */}</Animated.View>
</GestureDetector>
);
}
savedScaleに前回の拡大率を保存しておくことで、ピンチをやり直すたびに拡大率がリセットされず、直前の状態から続けて拡大縮小できます。
ダブルタップとシングルタップを共存させる
Twitterのタイムラインのように、シングルタップで詳細を開き、ダブルタップでいいねをする、といった複数のタップ数を使い分けるUIも作れます。Gesture.Tap()に.numberOfTaps()を指定し、Gesture.Exclusive()でシングルタップとダブルタップの優先順位を制御します。
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
function LikeableCard({ onOpen, onLike }: { onOpen: () => void; onLike: () => void }) {
const singleTap = Gesture.Tap()
.numberOfTaps(1)
.onEnd(() => onOpen());
const doubleTap = Gesture.Tap()
.numberOfTaps(2)
.onEnd(() => onLike());
// doubleTapが成立しない場合にのみsingleTapを発火させる
const composed = Gesture.Exclusive(doubleTap, singleTap);
return (
<GestureDetector gesture={composed}>
<View style={{ padding: 20 }}>{/* カード内容 */}</View>
</GestureDetector>
);
}
Gesture.Exclusive()は渡した順番が優先度になります。先に書いたdoubleTapが一定時間内に成立すればそちらが発火し、成立しなければsingleTapにフォールバックする、という関係を1行で表現できるのが便利なところです。
複数ジェスチャーの同時許可
ピンチしながら同時に回転させる、といった複数の指の動きを同時に受け付けたいケースではGesture.Simultaneous()を使います。
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
function PinchAndRotate() {
const scale = useSharedValue(1);
const rotation = useSharedValue(0);
const pinch = Gesture.Pinch().onUpdate((e) => {
scale.value = e.scale;
});
const rotate = Gesture.Rotation().onUpdate((e) => {
rotation.value = e.rotation;
});
// 2つのジェスチャーを同時に有効化する
const composed = Gesture.Simultaneous(pinch, rotate);
const style = useAnimatedStyle(() => ({
transform: [
{ scale: scale.value },
{ rotateZ: `${rotation.value}rad` },
],
}));
return (
<GestureDetector gesture={composed}>
<Animated.View style={style}>{/* 対象要素 */}</Animated.View>
</GestureDetector>
);
}
Gesture.Race()(早い者勝ちで1つだけ採用)、Gesture.Exclusive()(優先順位付き)、Gesture.Simultaneous()(すべて同時許可)の3種類を使い分けることで、指の本数や状況に応じたジェスチャーの競合関係を細かくコントロールできます。
まとめ
React Native Gesture Handlerは、React Nativeにおけるタッチ・ジェスチャー処理を、JavaScriptスレッドの混雑から切り離してくれるライブラリです。
- タッチ処理をUIスレッドで行うため、JSスレッドが重くてもジェスチャーが途切れない
Gesture.Pan()やGesture.Tap()など、宣言的でチェーン可能なAPISimultaneous・Race・Exclusiveによる柔軟なジェスチャーの競合制御- React Native Reanimatedと組み合わせることで、120fps級のなめらかなインタラクションを実現
スワイプ削除やピンチズームのようなよくあるUIパターンから、複数指を使う凝ったインタラクションまで、ジェスチャー周りで「思ったとおりに動かない」と感じたことがあるなら、導入を検討する価値は十分にあります。