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

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

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

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)