タッチUI
スマートフォンやタブレットなどタッチディスプレイに特化したUI要素やライブラリの探求
このページでわかること
- スワイプ・ピンチ・ドラッグ・ロングプレス・プルトゥリフレッシュの5つのタッチジェスチャー実装パターン
- @use-gesture/react・Framer Motion・react-swipeable・Pointer Events API による実装比較
- 判定閾値・物理シミュレーション・iOSの制約など実装時の注意点
- マウスとタッチの両対応、CSSの touch-action などの実践Tips
タッチUIとは
タッチUIは、スマートフォンやタブレットのタッチディスプレイで使われる、指の動きを入力とするUIインタラクション。マウス操作とは異なり、距離・速度・複数指の同時入力など多次元の情報を扱うため、ライブラリの選定が実装の精度を大きく左右する。
Web では Touch Events、Pointer Events、Gesture Events など複数のイベントAPIが存在し、ブラウザごとの挙動差にも注意が必要。React アプリでは抽象化されたフック(@use-gesture/react など)を使うのが一般的。
本ページでは、React アプリで実装する代表的なタッチジェスチャーを取り上げ、判定アルゴリズムや物理シミュレーションの違いをライブラリ別に比較する。
目的別の選び方
ジェスチャーごとに「判定の難しさ」「物理表現の必要性」が異なる。下表は典型的な選定基準。
| ジェスチャー | 判定難易度 | 物理表現 | 推奨ライブラリ | 主な用途 |
|---|---|---|---|---|
| スワイプ | 低 | 不要 | react-swipeable / @use-gesture | カルーセル・カード削除 |
| ピンチ | 中 | あれば自然 | @use-gesture + Framer Motion | 画像ズーム・地図 |
| ドラッグ | 中 | 必須 | Framer Motion | 並べ替え・カード移動 |
| ロングプレス | 低 | 不要 | @use-gesture | コンテキストメニュー |
| プルトゥリフレッシュ | 高 | 必須 | @use-gesture + 物理計算 | リスト更新 |
スワイプ(方向・速度判定)
詳細を見る →検知閾値・速度計算・方向ロックのアルゴリズムを主要ライブラリで比較
ピンチイン/アウト(2点間距離・スケール補正)
詳細を見る →2点間距離の計算精度・スケール補正の実装差を主要ライブラリで比較
ドラッグ(慣性・バウンド)
詳細を見る →物理シミュレーションの有無と精度・慣性アルゴリズムの実装差を主要ライブラリで比較
ロングプレス(タイムアウト・移動キャンセル判定)
詳細を見る →タイムアウト時間・移動キャンセル閾値の設定差と移動距離計算の精度を主要ライブラリで比較
プルトゥリフレッシュ(Pull to Refresh)
詳細を見る →プル検知アルゴリズム・インジケーターアニメーション・スナップバックの実装差を主要ライブラリで比較
実装の落とし穴
タッチUIはブラウザ・OS・端末ごとに挙動差があり、実装時にハマりやすいポイントがいくつかある。
- iOS Safari の Pull-to-Refresh ネイティブ動作との競合:
overscroll-behavior: containで抑制する - スワイプとスクロールの競合: 方向判定の精度が必要。
touch-action: pan-yで縦スクロールを許可しつつ横スワイプを取る - ピンチ操作時のページズーム抑制: 対象要素に
touch-action: noneを指定 - マウスイベントとの併存: Pointer Events API(pointerdown / pointermove / pointerup)で統一すると、
touch*とmouse*の重複ハンドリングが不要になる
よくある質問
Framer Motion と @use-gesture/react の使い分けは?
Framer Motion は物理アニメーションを含むドラッグ・スワイプの「見た目を動かす」のに強く、@use-gesture/react はジェスチャー検知に特化している。組み合わせて使うことも多く、@use-gesture で検知して Framer Motion で動かす構成がよく採用される。
タッチイベントとマウスイベントの両方に対応するには?
Pointer Events API(pointerdown / pointermove / pointerup)を使うか、それを内部で使うライブラリ(@use-gesture/react など)を採用するのが定石。touch* と mouse* を個別に書くと処理が重複しやすい。
スマホでスワイプすると意図せずスクロールしてしまう
CSS の touch-action プロパティで制御する。横スワイプを優先したい場合は touch-action: pan-y、ジェスチャーを完全制御したい場合は touch-action: none を指定する。
ロングプレスのデフォルト時間は何msがいい?
500ms 前後が一般的。@use-gesture では filterTaps: true と delay: 500 で設定する。短すぎるとタップと混同し、長すぎると操作がもたつくため、UI の用途に合わせて調整する。