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
のようにいい感じにプレビューされません。
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
を描画することで、ドラッグ時にプレビューを表示することができます。
スマホでもプレビューができた!
※ ChromeのDevToolsでスマートフォンをエミュレートして録画しているためマウスカーソルが表示されています。
最後に
スマートフォンなどのタッチデバイスでHTML5のようなドラッグアンドドロップを実現する方法を解説しました。
実際に実装する際は、TouchBackendのリポジトリ に完全に動作するサンプルがあるのでそちらも参考にしてみてください。
参考リンク
http://react-dnd.github.io/react-dnd/about
https://github.com/yahoo/react-dnd-touch-backend