Webページ上で要素をマウスでつかんで別の場所に移動させる「ドラッグ&ドロップ」。じつは HTML には draggable という属性が用意されていて、要素に付けるだけでドラッグの対象にできます。あとは JavaScript でいくつかのイベントを拾えば、「カードをかごに入れる」「並び替える」といった操作を実装できます。この記事では、draggable 属性の基本から、ドラッグ&ドロップを成立させるためのイベント、つまずきやすいポイントまで、動くデモとコード付きで初心者向けに解説します。
目次
draggable 属性でできること
draggable は、その要素を「マウスでつかんで動かせる(ドラッグできる)」ようにする属性です。値には true か false を明示的に指定します。a 要素や img 要素は初期状態でドラッグできますが、div などの一般的な要素は draggable="true" を付けて初めてドラッグの対象になります。
<!-- この div はドラッグできるようになる --> <div draggable="true">ドラッグしてね</div> <!-- 画像のドラッグを禁止したいときは false を指定 --> <img src="logo.png" alt="ロゴ" draggable="false">
注意したいのは、draggable="true" を付けただけでは「つかんで動かせる」だけで、ドロップ先に落とす処理は自動では行われないという点です。どこに落とせるのか、落としたら何をするのかは、JavaScript でイベントを処理して自分で決める必要があります。
ドラッグ&ドロップを成立させるイベント
ドラッグ&ドロップは、「つかむ側(ドラッグされる要素)」と「受け取る側(ドロップ先の要素)」の両方でイベントを拾うことで成り立ちます。主に使うイベントは次のとおりです。
| イベント | 発生するタイミング |
|---|---|
dragstart | 要素のドラッグを開始した瞬間(つかむ側で発生) |
dragend | ドラッグを終えた瞬間(つかむ側で発生) |
dragover | ドラッグ中の要素がドロップ先の上にある間、繰り返し発生(受け取る側) |
dragleave | ドラッグ中の要素がドロップ先から外れた(受け取る側) |
drop | ドロップ先で要素を離した瞬間(受け取る側) |
この中でとくに重要なのが dragover です。ブラウザは初期状態で「要素をドロップさせない」動作になっているため、dragover で event.preventDefault() を呼んでその既定動作を打ち消さないと、drop イベントが発生しません。ここがドラッグ&ドロップで最初につまずくポイントです。
動かして試す|カードをかごにドロップする
実際に動くデモを見てみましょう。左のカードをつかんで、右の「かご」の中にドラッグ&ドロップすると、カードがかごに移動します。ドラッグ中はカードが半透明になり、かごの上に乗っている間は枠の色が変わります。下の各タブで HTML・CSS・JavaScript のコードも確認できます。
<div class="board">
<div class="tray" id="tray">
<div class="card" draggable="true">🍎 りんご</div>
<div class="card" draggable="true">🍌 バナナ</div>
<div class="card" draggable="true">🍇 ぶどう</div>
</div>
<div class="basket" id="basket">
<p class="basket__label">ここにドラッグ&ドロップ</p>
</div>
</div>
.board {
display: flex;
gap: 16px;
font-family: sans-serif;
}
.tray,
.basket {
flex: 1;
min-height: 160px;
padding: 12px;
border: 2px dashed #bbb;
border-radius: 8px;
}
.card {
padding: 10px 14px;
margin-bottom: 8px;
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
cursor: grab; /* つかめることを示すカーソル */
}
/* ドラッグ中の要素を半透明にする */
.card.dragging {
opacity: 0.4;
}
/* ドロップできる場所であることを強調する */
.basket.over {
border-color: #007bff;
background: #eef6ff;
}
.basket__label {
margin: 0;
color: #888;
text-align: center;
}
const cards = document.querySelectorAll(".card");
const basket = document.getElementById("basket");
let draggingCard = null;
cards.forEach((card) => {
// ドラッグ開始:どの要素を運んでいるか覚えておく
card.addEventListener("dragstart", () => {
draggingCard = card;
card.classList.add("dragging");
});
// ドラッグ終了:状態を元に戻す
card.addEventListener("dragend", () => {
card.classList.remove("dragging");
draggingCard = null;
});
});
// preventDefault を呼ぶと、その要素が「ドロップ可能」になる
basket.addEventListener("dragover", (e) => {
e.preventDefault();
basket.classList.add("over");
});
basket.addEventListener("dragleave", () => {
basket.classList.remove("over");
});
// ドロップ:運んできた要素をかごの中へ移動する
basket.addEventListener("drop", (e) => {
e.preventDefault();
basket.classList.remove("over");
if (draggingCard) {
basket.appendChild(draggingCard);
}
});
ポイントは、dragstart でつかんだ要素を変数に覚えておき、drop のときにその要素を appendChild でかごへ移動させているところです。appendChild は「すでにある要素を渡すと、その要素を元の位置から移動させる」ため、これだけでカードがトレイからかごへ移ります。dragover での preventDefault() を忘れると drop が呼ばれないので、必ず入れておきましょう。
ドラッグするデータを受け渡す dataTransfer
先ほどのデモでは、つかんだ要素を変数に覚えておく方法を使いました。もう一つ、ドラッグ&ドロップには dataTransfer という「ドラッグ中のデータを運ぶための入れ物」が用意されています。dragstart で文字列などをセットし、drop で取り出す、という受け渡しができます。
const item = document.getElementById("item");
const dropzone = document.getElementById("dropzone");
item.addEventListener("dragstart", (e) => {
// 運びたいデータを詰める(第1引数はデータの種類)
e.dataTransfer.setData("text/plain", item.id);
});
dropzone.addEventListener("dragover", (e) => {
e.preventDefault(); // これがないと drop が発生しない
});
dropzone.addEventListener("drop", (e) => {
e.preventDefault();
// dragstart で詰めたデータを取り出す
const id = e.dataTransfer.getData("text/plain");
const dragged = document.getElementById(id);
dropzone.appendChild(dragged);
});
dataTransfer を使うと、ブラウザの外(デスクトップのファイルなど)や別のウィンドウとの受け渡しにも対応できます。ページ内で要素を動かすだけなら変数に覚えておくだけでも十分ですが、「テキストやIDといったデータを運びたい」ときは dataTransfer が役立ちます。
ドロップしても反応しないとき
dragover で preventDefault を呼んでいない
もっとも多い原因がこれです。前述のとおり、ブラウザは初期状態で要素のドロップを受け付けません。ドロップ先の dragover イベントで event.preventDefault() を呼んで、はじめて drop イベントが発生するようになります。「つかんで動かせるのに、離しても何も起きない」ときは、まず dragover の preventDefault() を確認してください。
ドラッグ対象に draggable=”true” が付いていない
a 要素や img 要素以外の一般的な要素は、draggable="true" を付けないとそもそもドラッグが始まりません。dragstart が発火しないときは、対象の要素に属性が付いているかを見直しましょう。また、要素内のテキストを選択した状態からドラッグすると、テキスト選択のドラッグと競合することがあります。
スマートフォン(タッチ操作)では動かない
HTML の draggable によるドラッグ&ドロップは、基本的にマウス操作向けの機能で、スマートフォンのタッチ操作では動作しません。スマホでも並び替えなどを実現したい場合は、touchstart などのタッチイベントを使って自前で実装するか、対応した専用ライブラリを利用するのが一般的です。用途に合わせて選びましょう。
まとめ
draggable="true" を付けると、その要素はマウスでつかんでドラッグできるようになります。ただしドロップ処理は自動では行われず、つかむ側で dragstart、受け取る側で dragover・drop といったイベントを処理して自分で組み立てます。とくに dragover での preventDefault() を忘れると drop が発生しない点に注意してください。データを運びたいときは dataTransfer、タッチ操作に対応したいときは別の実装が必要になる、という違いも押さえておくと、思ったとおりのドラッグ&ドロップが作れるようになります。