LoginSignup
24
14

More than 5 years have passed since last update.

React DnDでスマホでもドラッグアンドドロップ

Posted at

Enigmo Advent Calendar 2018の12日目の記事です。

注意: この記事のサンプルコードで使われている各ライブラリのバージョンは下記になります。

react 16.4.0
react-dnd 4.0.2
react-dnd-html5-backend 4.0.2
react-dnd-touch-backend 0.5.1

React DnD

Reactでドラッグアンドドロップでの並び替えを実装する際によく使われるのがReact DnDというライブラリです。
このライブラリではHTML5のDrag and Drop APIを利用してドラッグアンドドロップを実現していますが、このAPI自体がスマートフォンなどのタッチデバイスには対応しておらず、スマホでそのままドラッグアンドドロップを実装することができません。

TouchBackend

React DnDを使う際、ドラッグアンドドロップしたいコンポーネントを DragDropContext という HOC(Higer Order Component) に渡します。
この DragDropContext の最初の引数に渡すのは通常、 HTML5Backend というバックエンドモジュールです。

import HTML5Backend from 'react-dnd-html5-backend'
import { DragDropContext } from 'react-dnd'

class YourApp {
    /* ... */
}

export default DragDropContext(HTML5Backend)(YourApp)

前述した通りタッチデバイスの場合はこの HTML5Backend は使えません。 しかしタッチデバイス対応した TouchBackendというものがあるのでそちらを使います。

import HTML5Backend from 'react-dnd-html5-backend'
import TouchBackend from 'react-dnd-touch-backend';
import { DragDropContext } from 'react-dnd'

const isTouchDevice = () => {
 /* タッチデバイス判定 */
}

class YourApp {
    /* ... */
}
export default DragDropContext(isTouchDevice() ? TouchBackend : HTML5Backend)(YourApp)

これだけでタッチデバイス対応ができました。
しかし、 HTML5Backend のようにいい感じにプレビューされません。

pc-preview.gif
HTML5Backendではちゃんとプレビューされている


sp-nopreview.gif
TouchBackendではプレビューされていない!
※ ChromeのDevToolsでスマートフォンをエミュレートして録画しているためマウスカーソルが表示されています。

DragLayer

React DnD にはDragLayerという、ドラッグ時のプレビュー表示をカスタマイズできるAPIがあります。
これを使うことでタッチデバイスでもいい感じのプレビューを表示することができます。

利用側のサンプルコードは以下です。

import React from 'react'
import DragLayer from 'react-dnd/lib/DragLayer'
import TouchBackend from 'react-dnd-touch-backend';
import { DragDropContext } from 'react-dnd'

function collect(monitor) {
  const item = monitor.getItem()
  return {
    currentOffset: monitor.getSourceClientOffset(),
    previewProps: item && item.previewProps,
    isDragging:
      monitor.isDragging() && monitor.getItemType() === 'IMAGE'
  }
}

function getItemStyles(currentOffset) {
  if (!currentOffset) {
    return {
      display: 'none'
    }
  }

  const x = currentOffset.x
  const y = currentOffset.y
  const transform = `translate(${x}px, ${y}px) scale(1.05)`

  return {
    WebkitTransform: transform,
    transform: transform,
  }
}

class PreviewComponent extends React.Component {
  render() {
    const { isDragging, previewProps, currentOffset } = this.props
    if (!isDragging) {
      return null
    }

    return (
      <div style={getItemStyles(currentOffset)}>
        {/*...*/}
      </div>
    )
  }
}

const DragPreview = DragLayer(collect)(PreviewComponent)


class YourApp {
  render() {
    return (
      <div>
        {/* ... */}
        <DragPreview />
      </div>
    )
  }
}

export default DragDropContext(TouchBackend)(YourApp)

かんたんに解説

DragLayer の引数 collect 関数ではDragLayerMonitorのオブジェクトが渡されます。
monitor.getItem()DragSource にアクセスすることができ、 任意で渡した props(今回の場合は previewProps という名前で渡していますが、どんな名前でも渡すことができます) にアクセスできます。
また、 monitor.isDragging で実際にドラッグされているか判定することができます。
同一画面の他のコンポーネントでもドラッグアンドドロップするために、 DragDropContext が複数ある場合は monitor.getItemType() でどのコンテキストなのかを判定するとよいでしょう。
プレビューがタッチした部分に追従するように monitor.getSourceClientOffset() を使ってオフセット座標を返しておきます。
collect 関数の返り値のオブジェクトはそのままプレビュー用のコンポーネントで props として受け取ることができます。
getItemStyles 関数では受け取った props.currentOffset を使ってCSSを調整しています。

DragDropContext に渡したコンポーネントで DragLayer を描画することで、ドラッグ時にプレビューを表示することができます。


sp-preview.gif
スマホでもプレビューができた!
※ ChromeのDevToolsでスマートフォンをエミュレートして録画しているためマウスカーソルが表示されています。

最後に

スマートフォンなどのタッチデバイスでHTML5のようなドラッグアンドドロップを実現する方法を解説しました。
実際に実装する際は、TouchBackendのリポジトリ に完全に動作するサンプルがあるのでそちらも参考にしてみてください。

参考リンク

http://react-dnd.github.io/react-dnd/about
https://github.com/yahoo/react-dnd-touch-backend

24
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
14