LoginSignup
23
20

More than 3 years have passed since last update.

【HTML】ドラッグ&ドロップについて復習する【標準API】

Posted at

HTML ドラッグ&ドロップ API とは?

HTML ドラッグ & ドロップインターフェイスにより、アプリケーションはブラウザーでドラッグ&ドロップ機能を使用することができます。ユーザーはマウスでドラッグ可能な要素を選択し、その要素をドロップ可能な要素へドラッグし、マウスボタンを離すことでドロップすることができます。ドラッグ操作の間、ドラッグ可能な要素の半透明の表示がマウスポインターに続きます。

引用:HTML ドラッグ&ドロップ API - Web API _ MDN

実装してみる

まずはプレーンなHTMLcssのみで実装してみる。
JavaScriptによる実装は何も無いので、この状態ではまだ要素をドラッグできるだけである。


See the Pen
lesson1
by naoki-funawatari (@naoki-funawatari)
on CodePen.


ドラッグされるアイテムの実装

まず、ドラッグされるアイテムの実装を行っていく。

  • draggable="true"が設定された要素がドラッグの対象となる。

ドラッグの開始

  • ドラッグが開始されたことを検知するためにはdragstartイベントを実装する必要がある。
  • ここでは、ドラッグされた要素に対してdraggingクラスを設定している。 要素が半透明の状態になりドラッグ中であることが分かる。

ドラッグの終了

  • ドラッグが終了したことを検知するためにはdragendイベントを実装する必要がある。
  • ドラッグ終了時に要素からdraggingクラスを削除することで、半透明の状態が解除される。

ここまでのまとめ

  • 以下がdragstartdragendイベントを実装した状態である。
  • ドラッグ開始時に要素が半透明になり、ドラッグ終了時に要素の半透明が解除される。

    See the Pen lesson2 by naoki-funawatari (@naoki-funawatari) on CodePen.

ドロップ先の実装

ここから、ドラッグされたアイテムがドロップされる先の実装を行っていく。

要素の重なり

  • ドラッグアイテムがドロップ先と重なったことを検知するためにはdragenterを実装する必要がある。
  • ここでは、ドラッグされた要素に対してoverクラスを設定している。
  • 要素の背景色が淡黄色の状態になり要素が重なっていることが分かる。

要素の重なりを解除

  • ドラッグアイテムとドロップ先が離れたことを検知するためにはdragleaveイベントを実装する必要がある。
  • ドロップ先の要素からoverクラスを削除することで、淡黄色の状態が解除される。

ここまでのまとめ

  • 以下がdragenterdragleaveイベントを実装した状態である。
  • 要素が重なった際に背景色が淡黄色となり、要素が離れた際に背景色が元に戻る。

    See the Pen lesson3 by naoki-funawatari (@naoki-funawatari) on CodePen.

ドロップ処理の実装

ここから、実際にドロップされたときの処理を実装していく。

ブラウザ既定の処理を変更

  • dragoverdropの各イベント内にevent.preventDefault()メソッドの記述を追加する。
  • そうすることでドラッグしている最中、ドロップ時に発生するブラウザ既定のイベントをキャンセルできる。
  • ドロップ先の要素にドロップゾーンとしての役割を持たせるために必須の処理となる。
  • これは、例えばブラウザの外からファイルをドロップした際、JavaScriptでファイルを受け取りたい場合などにも役に立つ。 この処理を実装しなかった場合、単純にブラウザで当該ファイルが展開されてしまう。

要素のドロップ

  • 要素がドロップされたことを検知するためにはdropイベントを実装する必要がある。
  • ここでは、ok, dropped!!とアラートを表示している。
  • 同時にドロップ先の要素からoverクラスを削除することで、淡黄色の状態が解除される。
  • ドロップ時にはdragleaveイベントは発生しないため、dropイベント内でも同様処理が必要となる。

ここまでのまとめ

  • 以下がdragoverdropイベントを実装した状態である。
  • ブラウザ既定のイベントを解除し、要素がドロップされた際に背景色が元に戻る。

    See the Pen lesson4 by naoki-funawatari (@naoki-funawatari) on CodePen.

要素の移動を実装

  • 要素の移動を実装するにはdragstartdragoverdropイベントをカスタマイズする必要がある。

ドロップ効果の設定

  • dragstartイベントのevent.dataTransfer.effectAllowedプロパティ、 dragoverイベントのevent.dataTransfer.dropEffectプロパティに対し、 copymovelinknoneetc...などいずれかの設定値を指定する。
  • ドラッグ要素、ドロップ先のそれぞれに指定したドロップ効果が一致している場合のみ、ドロップ処理が実行される。 今回はmoveを指定する。

データの受け渡し

  • dragstartイベントのevent.dataTransfer.setData()メソッドで転送するデータを設定する。
  • dropイベントのevent.dataTransfer.getData()メソッドでデータを取得する。
  • setData()getData()メソッドにはそれぞれMIMEタイプの指定が必要であり、ドロップ効果と同様に一致させておく必要がある。
  • 例としては"text/plain""application/json"などがある。
  • 今回は"application/json"を指定し、json形式で要素のidを転送する。

要素の移動

  • 転送されたドラッグ要素のidを元にdocument.getElementByIdメソッドで要素を取得し、 ドロップ先にappendChildメソッドで要素を追加することで要素の移動が実装完了となる。
  • 移動元から要素を削除する、といった処理は不要。

  • ちなみにappendChildメソッドによる要素の移動は、本記事のdraggableとは無関係であり、通常のJavaScriptの挙動である。

  • draggableによって実現される機能としては、前述の各イベントや、setData()getData()メソッドによるデータの転送がメインとなる。

要素の移動自体は単純にJavaScriptの機能
const item1 = document.querySelector("#item1");
const box2 = document.querySelector(".box2");
box2.appendChild(item1);

ここまでのまとめ

  • 以下がdragstartdragoverdropイベントを実装した状態である。
  • dragstartイベントでドラッグ要素のidを転送、 dropイベントでidを取得し、それを元に要素を取得、及びドロップ先へ要素の追加を行っている

    See the Pen lesson5 by naoki-funawatari (@naoki-funawatari) on CodePen.

問題点

ここまでの実装でドラッグ&ドロップに関する実装は一通り完了したが、一つ問題が残っている。
ドロップ先の子要素への移動、つまりitemからitemへ移動できてしまう。

子要素への移動を制限

  • 子要素への移動を制限するにはdragenterdragoverイベントをカスタマイズする必要がある。
  • ドラッグした要素がitemであればドラッグを許可しない、といった形でreturnしてしまう。
dragenterイベント
// 子要素へのドラッグを制限
if ([...e.target.classList].includes("item")) {
  return;
}
dragoverイベント
// 子要素へのドラッグを制限
if ([...e.target.classList].includes("item")) {
  // ドラッグ不可のドロップ効果を設定
  e.dataTransfer.dropEffect = "none";
  return;
}

ブラウザ外からのファイルのドロップを制限

ついでにやる。

  • ブラウザ外からのファイルのドロップを制限するにはdropイベントをカスタマイズする必要がある。
  • ドロップされたファイル数が0件を超えていたら後続の処理を実行しない、といった形でreturnしてしまう。
  • event.preventDefault()メソッドを実行しているためブラウザ上にファイルが展開されるといったことは発生しないが、event.dataTransfer.getData()メソッドから取得したデータを元に要素の移動などを実行する処理で例外が発生する。
dropイベント
// ブラウザ外からのファイルドロップを制限
if (e.dataTransfer.files.length > 0) {
  return;
}

おそらく完成

これで必要な機能は揃ったと思う。

See the Pen lesson6 by naoki-funawatari (@naoki-funawatari) on CodePen.

応用

ここまで要素の移動のみを実装してきた。
実装次第ではそれ以外のことももちろん可能。

要素の複製

すぐに思いつくのは要素の複製など。
コントロールキーを押下しながらドロップした場合に要素が複製される、といった形で実装してみる。

  • dragstartイベント内のドロップ効果を変更する。 移動と複製ができるようにする。
event.dataTransfer.effectAllowed = "copyMove";
  • dragoverイベント内に分岐を追加し、ドロップ効果を切り替える。
// ドロップ効果の設定
if (event.ctrlKey) {
  e.dataTransfer.dropEffect = "copy";
} else {
  e.dataTransfer.dropEffect = "move";
}
  • dropイベント内に分岐を追加し、移動処理と複製処理を切り替える。
  • cloneNode()メソッドではどうやらイベントリスナーは引き継がれないようなので、個別に設定する。
if (event.ctrlKey) {
  // 要素の複製
  const oldItem = document.getElementById(id);
  const newItem = oldItem.cloneNode(true);
  const newId = `item${[...document.querySelectorAll(".item")].length + 1}`;
  newItem.id = newId;
  newItem.classList.remove("dragging");

  // cloneNode() で引き継げない要素
  newItem.addEventListener("dragstart", handleDragStart, false);
  newItem.addEventListener("dragend", handleDragEnd, false);

  // ドロップ先に要素を追加する
  e.target.appendChild(newItem);
} else {
  // 要素の移動
  // ドロップ先に要素を追加する
  e.target.appendChild(document.getElementById(id));
}

全部入り

See the Pen lesson7 by naoki-funawatari (@naoki-funawatari) on CodePen.

精神力がほぼ底を尽きかけたために後半はかなり適当。

23
20
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
23
20