はじめに
「マウス操作でリストの順番を変えられるようにしたい」「要素を直感的に動かせる UI を作りたい」と考えたことはありませんか?
実際の現場や開発プロジェクトでは、ユーザーが自由に並べ替えられるリストや要素移動の仕組みは非常に便利です。例えば、タスク管理ツールや ToDo リスト、EC サイトの商品の表示順変更など、「ドラッグしてそのまま目的の場所へ落とす」操作は直感的で分かりやすいですよね。
こうした「ドラッグ&ドロップ(Drag & Drop)」機能は、HTML5 以降の標準 API を利用することで意外に簡単に実現できます。外部ライブラリを使わなくても、純粋な JavaScript と HTML だけで完結するのがポイントです。
この記事では、初心者〜中級者の方が「HTML5 の Drag & Drop API」を使って、リストの要素をドラッグ&ドロップで並び替える機能を実装できるようになるまでを詳しく解説します。「そもそもどうやるのかわからない」「実装のポイントがつかめない」という方の疑問に寄り添いつつ、実際に動くコード例も掲載しますので、ぜひ最後までご覧ください。
HTML5 Drag & Drop API の基本仕組みと実装ステップ
1. Drag & Drop API とは
HTML5 で導入された「Drag & Drop API」は、要素をドラッグ(つかんで移動)し、別の場所へドロップ(落とす)する処理をブラウザ標準でサポートする仕組みです。具体的には下記のようなイベントが用意されています。
-
dragstart: 要素のドラッグを開始したときに発火するイベント
-
dragover: ドラッグしている要素が対象要素の上を通過している間、継続的に発火するイベント
-
drop: ドラッグ中の要素がドロップされたときに発火するイベント
-
dragend: ドラッグ操作が終了(ドロップまたはキャンセル)したときに発火するイベント
ほかにも細かなイベントがありますが、最小限のドラッグ&ドロップ処理を実装するだけなら、上記 4 つが押さえどころになります。
2. 実装の流れ
Drag & Drop で並び替えを行うとき、大まかに以下のステップを踏みます。
-
ドラッグ対象の要素に draggable 属性を付ける
-
これにより、要素をマウスでドラッグできるようになります。
-
-
dragstart でドラッグするデータをセットする
-
e.dataTransfer.setData(“text/plain”, 何かの値) のように、ドラッグ対象に紐づく情報を保持します。今回であればリストアイテムのテキストが簡単な例です。
-
-
ドロップ先で dragover イベントに対して e.preventDefault() を呼ぶ
-
dragover 中に preventDefault() を呼ばないと、drop イベントが発火しません。ここが意外とハマりやすいポイント。
-
-
drop でドラッグしてきた要素の並び替えを行う
-
drop イベント内で e.dataTransfer.getData(…) を用いて、ドラッグ開始時にセットしたデータ(今回はテキスト)を取り出します。
-
それをもとに「どの要素をドラッグしたのか」「どこにドロップされたか」を特定し、リストの並び替えを実行します。
-
実際のコード例とよくある疑問の解消
1. 動くサンプルコード
それでは、実際にドラッグ&ドロップでリストの並び替えを行う最小限の実装例を示します。以下のコードを「dragdrop.html」という名前で保存してブラウザで開くだけで、ドラッグによる並び替えを確認できます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>Drag & Drop Example</title>
<style>
body {
font-family: sans-serif;
margin: 20px;
}
#js-result {
margin-top: 20px;
}
li {
list-style: none; /* リストアイテムのディスク(●)を非表示にする */
}
</style>
</head>
<body>
<h1>Drag & Drop Example</h1>
<div id="js-result"></div>
<script>
/**
* dragDropExample:
* 画面上にドラッグ&ドロップ可能なリストアイテムを生成し、
* ドラッグ操作でリストの並び替えを行えるようにする関数。
*/
const dragDropExample = () => {
// ドラッグ&ドロップ用の要素を格納する領域をクリアしておく
const resultDiv = document.getElementById("js-result");
resultDiv.innerHTML = "";
// UL 要素を作成し、ドロップ対象として扱いやすいようスタイル設定
const draggableList = document.createElement("ul");
draggableList.style.listStyle = "none";
draggableList.style.padding = "0";
// 5つのリストアイテムを作成
for (let i = 1; i <= 5; i++) {
const listItem = document.createElement("li");
listItem.textContent = `Item ${i}`;
listItem.style.padding = "10px";
listItem.style.margin = "5px";
listItem.style.backgroundColor = "#f0f0f0";
listItem.style.cursor = "move";
// HTML5のDrag & Drop APIを有効にする
listItem.draggable = true;
/**
* dragstart イベント:
* ドラッグが開始されたタイミングで発生。
* e.dataTransferにドラッグ対象のデータ(今回は文字列)をセットする。
*/
listItem.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", e.target.textContent);
// ドラッグ中は要素を半透明にするなどの視覚的効果を与える
e.target.style.opacity = "0.5";
});
/**
* dragend イベント:
* ドラッグが終了(ドロップまたはキャンセル)したときに発火。
* dragstartで下げたopacityを元に戻す。
*/
listItem.addEventListener("dragend", (e) => {
e.target.style.opacity = "1";
});
// 作成したリストアイテムを <ul> 内に追加
draggableList.appendChild(listItem);
}
/**
* dragover イベント:
* ドラッグ中に、アイテムがこの要素上を通過している間、継続的に発火。
* e.preventDefault() を呼ばないと drop イベントが発生しない。
*/
draggableList.addEventListener("dragover", (e) => {
e.preventDefault();
});
/**
* drop イベント:
* ドロップが行われた際に発生。
* ここで、dragstart でセットしたデータを取得し、
* ドロップ先の要素を取得して並び替えを実行する。
*/
draggableList.addEventListener("drop", (e) => {
e.preventDefault();
// dragstartでセットしたデータを取得
const data = e.dataTransfer.getData("text/plain");
// dropされた場所が <li> 要素かどうかを closest() で遡って調べる
const dropTarget = e.target.closest("li");
if (dropTarget) {
// ドラッグしているアイテムを探す
const draggedItem = Array.from(draggableList.children).find(
(item) => item.textContent === data
);
// ドロップ先が自分自身ではない場合のみ入れ替え処理
if (draggedItem !== dropTarget) {
// dropTargetの後ろに挿入することで自然な動作
draggableList.insertBefore(draggedItem, dropTarget.nextSibling);
}
}
});
// 生成した <ul> を #js-result に追加
resultDiv.appendChild(draggableList);
};
// ページ読み込み後に Drag & Drop を初期化
window.addEventListener("DOMContentLoaded", () => {
dragDropExample();
});
</script>
</body>
</html>
2. 「ドラッグがうまく動かない」よくある原因
-
draggable 属性の付け忘れ
要素をドラッグ可能にするには、draggable=”true” が必須です。JavaScript では element.draggable = true と設定しても OK。 -
dragover の preventDefault() を忘れている
dragover イベントで e.preventDefault() を呼ばないと、ブラウザはデフォルトの動作(例えばテキスト選択など)を行い、drop イベントが発生しません。 -
dataTransfer でセットしたデータを取得していない
dragstart イベントで e.dataTransfer.setData(…) したら、drop イベント側で必ず getData(…) を呼んで取り出す必要があります。ここが噛み合わないと要素の特定ができません。
3. ドラッグ要素を特定する方法
上記のコードでは、ドロップ時に「ドラッグ中のリストアイテムかどうか」を文字列(text/plain)で特定しています。しかし、文字列だとユニークな値がないと判別が難しい場合もあります。そこで、リスト要素に ID 属性やデータ属性を持たせて、それを setData / getData で扱うようにするとより正確な制御が可能です。
// 例: dataTransfer で "data-id" をセットする
listItem.dataset.id = i; // i はループ番号
listItem.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", e.target.dataset.id);
});
今すぐドラッグ&ドロップを試してみよう!
ここまでご覧いただき、「ドラッグ&ドロップによるリスト並び替えの仕組み」を少しでもイメージできたでしょうか?
-
Drag & Drop API は HTML5 標準の機能なので、追加ライブラリを入れずに使えます。
-
ただし、ブラウザ間で微妙に挙動の違いがあったり、draggable に対応していない要素があったりするため、使い所や注意点を把握することが大切です。
-
このサンプルコードを少し拡張すれば、タスク管理アプリや画像ギャラリーの並べ替えなど、多様なユースケースに応用できます。
次のステップ
-
実際にコードをコピペして動かしてみる
まずはお手元のエディタに上記の HTML を貼り付けて、ドラッグしてみてください。 -
データ属性や ID でドラッグ要素を一意に管理
より複雑なリスト並び替えを行うなら、テキスト情報ではなく一意の ID を持たせましょう。 -
スタイリングを工夫して分かりやすい UI に
ドラッグ中の枠線表示、ドロップ先のハイライトなどを追加すると、見た目の完成度が上がります。 -
フレームワークとの連携を試す
Vue.js や React などでも同様に Drag & Drop は使えます。ただし、仮想 DOM との兼ね合いをよく理解して実装してください。
もし「もっと高度なドラッグ&ドロップをやりたい」「ドラッグ要素を別のコンテナへ移動したい」などの要望がある場合は、UI ライブラリの活用や、パフォーマンス最適化の手法などを調べてみるとよいでしょう。
さらに詳しく学びたい方は、MDN(Mozilla Developer Network)の Drag and Drop API ページが公式ドキュメントとして充実していますので、併せてご参照ください。