Drag & Drop API を使うときにやっておいたほうがいいこと

# Drag and Drop API について

最近のブラウザはライブラリを使わなくてもブラウザ標準APIでドラッグ＆ドロップが実現できます

ブラウザの対応状況を見みるとPC用Webブラウザであればほとんど対応しています。

[Drag and Drop - Can I use](https://caniuse.com/#feat=dragndrop)

このAPIを使って[こんな感じなもの](https://boushi-bird.github.io/xmas-game/)が作れます。
![screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/21396/55705b75-3c95-6c14-4a22-01b37030b3f3.png)


実際に使ってみて [Drag & Drop API](https://developer.mozilla.org/ja/docs/Web/API/HTML_Drag_and_Drop_API) を使うときにやっておいた方がよさそうなことを紹介します。

また、iOS/Android でも Drag & Drop API を使う方法も紹介します。

Drag & Drop APIの使い方については参考サイトに任せます。

## 参考サイト

  - [意外と知らないHTML5 API - Drag & Drop APIとは](https://app.codegrid.net/entry/dnd-api-1)
  - [ネイティブ HTML5 ドラッグ＆ドロップ](https://www.html5rocks.com/ja/tutorials/dnd/basics/)

# やっておいたほうがいいこと

## ドラッグ対象以外の画像のドラッグを無効にする

imgタグははデフォルトでdraggableが有効になっています。

本来ドラッグさせたい要素以外がドラッグできてしまうと
操作するユーザの混乱の元になります。

そこで明示的にdraggable="true"にしていない要素をドラッグできないようにします。

```js
document.addEventListener('dragstart', function(event) {
  const draggable = event.target.getAttribute('draggable');
  // 明示的にdraggableにしている要素以外ドラッグ禁止
  if (!draggable || draggable === 'auto') {
    event.preventDefault();
  }
});
```

Webkit系ブラウザであればcssだけでもドラッグを無効にできます

```css
img {
  -webkit-user-drag: none;
}
img[draggable=true] {
  -webkit-user-drag: auto;
}
```

## ドラッグできることをわかりやすくする。

カーソルを合わせたときにポインタを変えます

```css
*[draggable=true] {
  cursor: pointer;
}
```

## ユーザ選択を無効にする

ドラッグ操作をさせるときに、文字などの部分が選択できてしまい煩わしいことがあります。

そういう箇所はcss文字選択できないようにします。

[user-select
](https://developer.mozilla.org/ja/docs/Web/CSS/user-select)

```css
.not_select {
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
```

## IE向けに、DataTransfer.setData() , DataTransfer.getData() の format に / (スラッシュ)を使わない

リファレンス通り、 `event.dataTransfer.setData('text/plain', data);`  とかやってたのですがIE11だと / (スラッシュ) 付きはエラーになりました。

IEを考慮する場合は `event.dataTransfer.setData('text', data);` などとした方が良さそうです。

## iOS/Android でも使えるようにする

[Drag and Drop - Can I use](https://caniuse.com/#feat=dragndrop) を見ると、iOS11以降の safariは使えるが Android では使えないように見えます。

しかし、実際に手持ち端末で試したところ、

iOS12のSafariでは全く使えず、(MacのiPhoneシミュレータ上のSafariでも同様)、
逆にAndroid8、Android7のChromeではほとんど(※)動作しました。

※Androidではなぜか`DataTransfer.effectAllowed`に `link` `copyLink` `linkMove` といずれも link が含まれていると使えませんでした。

スマホで使うにあたっては Polyfill を使います。

[Polyfill for HTML 5 drag'n'drop](https://github.com/timruffles/mobile-drag-drop)

Webpackなどを使っているときの手順です。

```shell
npm install mobile-drag-drop --save
```

まず、 Polyfill用ファイル(仮に`dnd-polyfill.js` とします)を作り、読み込ませます。なぜ別ファイルにするかというと、将来的に対象ブラウザが全てネイティブで実装された場合、これだけ取り除けば良いようにするためです。

index.js

```javascript
import './dnd-polyfill';

// ....
```

dnd-polyfill.js

```javascript
import { polyfill } from 'mobile-drag-drop';
import { scrollBehaviourDragImageTranslateOverride } from 'mobile-drag-drop/scroll-behaviour';

// Webpackなどでcssを読み込めるようにしている場合
// js内で読みこめない場合は普通にlinkタグで読み込んでください。
import 'mobile-drag-drop/default.css';

// iOS/Androidのときだけ、usePolyfill=trueになる
const usePolyfill = polyfill({
  dragImageTranslateOverride: scrollBehaviourDragImageTranslateOverride,
});

if (usePolyfill) {
  // https://github.com/timruffles/mobile-drag-drop#polyfill-requires-dragenter-listener
  // このpolyfill使用の場合 dragenter イベント時に Event.preventDefault() を呼ぶ必要がある
  document.addEventListener('dragenter', function(event) {
    event.preventDefault();
  });
  // https://github.com/timruffles/mobile-drag-drop/issues/77
  window.addEventListener('touchmove', function() {}, { passive: false });
}
```

ネイティブで Drag & Drop API が使える Androidで実際に操作してみると、タイミングによって ネイティブのイベントが発生するときがあります。

どちらでも ドラッグ＆ドロップ は動作するのですが、先程書いた `DataTransfer.effectAllowed` のlink関連はネイティブ側のイベントだと動きません。

linkを使わなければこのままでも問題ありませんが、やや気持ち悪いので Polyfillが有効の場合にはネイティブのイベントを無効にします。

dnd-polyfill.js

```javascript
// ...

const usePolyfill = polyfill({
  dragImageTranslateOverride: scrollBehaviourDragImageTranslateOverride,
});

if (usePolyfill) {
  document.addEventListener('dragenter', function(event) {
    event.preventDefault();
  });
  window.addEventListener('touchmove', function() {}, { passive: false });

  // これを追加
  document.addEventListener('dragstart', function(event) {
    if (!event.isTrusted) {
      return;
    }
    event.stopPropagation();
  }, true);
}
```

## iOS/Android で画像を含むドラッグ対象の長押し時の動作を無効にする。

画像を含むドラッグ対象の要素は、長押しと判別され、画像の保存などのダイアログが出てきてしまします。

iOSの場合はcssだけでそれを無効にできます。

```css
*[draggable=true] img {
  -webkit-touch-callout: none;
}
```

しかし、Androidはこれをやってもだめのようだったので無理やりこんな感じで画像長押しを防止します。

```html
<div class="contains-image" draggable="true">
  <img src="test.png" />
  <div class="protect-image"></div>
</div>
```

```css
.contains-image {
  position: relative;
}

.protect-image {
  width: 100%;
  height: 100%;
  position: absolute;
  padding: 0;
  border: 0;
  top: 0;
  left: 0;
  opacity: 0;
}
```

画像の上に透明な要素をおいて画像の長押しを防いでいます。

# リンク集

- [Drag & Drop API](https://developer.mozilla.org/ja/docs/Web/API/HTML_Drag_and_Drop_API)
- [Drag and Drop - Can I use](https://caniuse.com/#feat=dragndrop)
- [意外と知らないHTML5 API - Drag & Drop APIとは](https://app.codegrid.net/entry/dnd-api-1)
- [ネイティブ HTML5 ドラッグ＆ドロップ](https://www.html5rocks.com/ja/tutorials/dnd/basics/)
- [Polyfill for HTML 5 drag'n'drop](https://github.com/timruffles/mobile-drag-drop)