JavaScript
HTML5

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

Drag and Drop API について

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

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

Drag and Drop - Can I use

このAPIを使ってこんな感じなものが作れます。
screenshot.png

実際に使ってみて Drag & Drop API を使うときにやっておいた方がよさそうなことを紹介します。

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

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

参考サイト

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

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

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

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

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

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

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

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

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

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

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

ユーザ選択を無効にする

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

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

user-select

.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 を見ると、iOS11以降の safariは使えるが Android では使えないように見えます。

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

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

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

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

Polyfill for HTML 5 drag'n'drop

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

npm install mobile-drag-drop --save

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

index.js

import './dnd-polyfill';

// ....

dnd-polyfill.js

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

// ...

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だけでそれを無効にできます。

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

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

<div class="contains-image" draggable="true">
  <img src="test.png" />
  <div class="protect-image"></div>
</div>
.contains-image {
  position: relative;
}

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

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

リンク集