JavaScriptドラッグ&ドロップ完全攻略!10のサンプルコードでマスター

JavaScriptでドラッグ&ドロップを実装するイメージJS
この記事は約19分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればサンプルコードを活用して機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めば、JavaScriptでドラッグ&ドロップを実装する方法が身に付くことでしょう。

サンプルコードを交えながら、初心者にもわかりやすく徹底解説していきます。

応用例や注意点、カスタマイズ方法も紹介するので、ぜひ最後までお付き合いください。

●基本的なドラッグ&ドロップの仕組み

ドラッグ&ドロップは、マウスやタッチ操作で要素を移動させる一般的な操作です。

これを実現するには、HTML要素に対してドラッグイベントとドロップイベントを監視し、イベント発生時の処理を記述することで実現します。

●JavaScriptでドラッグ&ドロップを実装する方法

ここでは、基本的なドラッグ&ドロップの実装方法を紹介します。

この例では、ドラッグ対象の要素とドロップ対象の要素にイベントリスナを設定して、ドラッグ&ドロップの操作を実現しています。

○サンプルコード1:基本的なドラッグ&ドロップ

<!DOCTYPE html>
<html>
<head>
<style>
  .draggable {
    width: 100px;
    height: 100px;
    background-color: red;
    cursor: move;
  }
  .droppable {
    width: 200px;
    height: 200px;
    background-color: blue;
  }
</style>
</head>
<body>

<div class="draggable" draggable="true" id="drag1"></div>
<div class="droppable" id="drop1"></div>

<script>
  const dragElem = document.getElementById("drag1");
  const dropElem = document.getElementById("drop1");

  // ドラッグ開始時の処理
  dragElem.addEventListener("dragstart", (e) => {
    e.dataTransfer.setData("text/plain", e.target.id);
  });

  // ドロップ対象に入った時の処理
  dropElem.addEventListener("dragover", (e) => {
    e.preventDefault();
  });

  // ドロップ時の処理
  dropElem.addEventListener("drop", (e) => {
    e.preventDefault();
    const id = e.dataTransfer.getData("text/plain");
    e.target.appendChild(document.getElementById(id));
  });
</script>

</body>
</html>

このコードでは、赤い四角形の要素をドラッグして青い四角形の要素にドロップする操作ができます。

ドラッグ開始時に要素のIDをデータとして設定し、ドロップ時にそのデータを取得して要素を移動させる処理を記述しています。

●応用例1:要素の並び替え

ドラッグ&ドロップを応用して、リスト内のアイテムを並び替えることができます。

次のサンプルコードでは、リスト内のアイテムをドラッグして別の場所にドロップすることで並び替えが可能になっています。

○サンプルコード2:リストアイテムの並び替え

<!DOCTYPE html>
<html>
<head>
<style>
  .list-item {
    padding: 10px;
    margin: 5px;
    border: 1px solid black;
    cursor: move;
  }
</style>
</head>
<body>

<ul id="list">
  <li class="list-item" draggable="true">アイテム1</li>
  <li class="list-item" draggable="true">アイテム2</li>
  <li class="list-item" draggable="true">アイテム3</li>
</ul>

<script>
  const list = document.getElementById("list");
  let dragTarget;

  // ドラッグ開始時の処理
  list.addEventListener("dragstart", (e) => {
    dragTarget = e.target;
  });

  // ドロップ対象に入った時の処理
  list.addEventListener("dragover", (e) => {
    e.preventDefault();

    const dropTarget = e.target.closest(".list-item");
    if (!dropTarget || dropTarget === dragTarget) return;

    const rect = dropTarget.getBoundingClientRect();
    const middleY = (rect.top + rect.bottom) / 2;

    if (e.clientY < middleY) {
      list.insertBefore(dragTarget, dropTarget);
    } else {
      list.insertBefore(dragTarget, dropTarget.nextSibling);
    }
  });
</script>

</body>
</html>

このコードでは、リストアイテムをドラッグして別の場所にドロップすることで並び替えを実現しています。

ドラッグ対象とドロップ対象が同じ場合は、何も処理を行わずに終了しています。

ドロップ対象の上下にドラッグ対象を挿入する処理を記述しています。

●応用例2:ファイルアップロード

ドラッグ&ドロップを用いて、ファイルのアップロード操作を実現することもできます。

次のサンプルコードでは、ドロップされたファイルの情報を取得し、表示しています。

○サンプルコード3:ドロップされたファイルの情報取得

<!DOCTYPE html>
<html>
<head>
<style>
  .drop-zone {
    width: 300px;
    height: 200px;
    border: 2px dashed black;
    text-align: center;
    padding: 20px;
  }
</style>
</head>
<body>

<div class="drop-zone" id="dropZone">ここにファイルをドロップ</div>
<pre id="fileInfo"></pre>

<script>
  const dropZone = document.getElementById("dropZone");
  const fileInfo = document.getElementById("fileInfo");

  // ドロップ対象に入った時の処理
  dropZone.addEventListener("dragover", (e) => {
    e.preventDefault();
  });

  // ドロップ時の処理
  dropZone.addEventListener("drop", (e) => {
    e.preventDefault();
    const files = e.dataTransfer.files;

    for (const file of files) {
      fileInfo.innerHTML += `ファイル名:${file.name}, サイズ:${file.size}バイト\n`;
    }
  });
</script>

</body>
</html>

このサンプルコードでは、ドロップされたファイルの情報を取得し、その情報を表示するコードを紹介しています。

ドロップ時にファイルの情報を取得し、ファイル名やサイズを表示しています。

●応用例3:カスタム要素のドラッグ&ドロップ

カスタム要素に対しても、ドラッグ&ドロップ機能を実装することができます。

次のサンプルコードでは、カスタム要素をドラッグ&ドロップできるようにしています。

○サンプルコード4:カスタム要素のドラッグ&ドロップ

<!DOCTYPE html>
<html>
<head>
<style>
  custom-box {
    display: inline-block;
    width: 100px;
    height: 100px;
    background-color: green;
    margin: 5px;
    cursor: move;
  }
</style>
</head>
<body>

<custom-box draggable="true" id="custom1"></custom-box>
<custom-box draggable="true" id="custom2"></custom-box>

<script>
  const custom1 = document.getElementById("custom1");
  const custom2 = document.getElementById("custom2");

  // ドラッグ開始時の処理
  custom1.addEventListener("dragstart", (e) => {
    e.dataTransfer.setData("text/plain", e.target.id);
  });

  custom2.addEventListener("dragstart", (e) => {
    e.dataTransfer.setData("text/plain", e.target.id);
  });

  // ドロップ対象に入った時の処理
  custom1.addEventListener("dragover", (e) => {
    e.preventDefault();
  });

  custom2.addEventListener("dragover", (e) => {
    e.preventDefault();
  });

  // ドロップ時の処理
  custom1.addEventListener("drop", (e) => {
    e.preventDefault();
    const id = e.dataTransfer.getData("text/plain");
    e.target.insertAdjacentElement("beforebegin", document.getElementById(id));
  });

  custom2.addEventListener("drop", (e) => {
    e.preventDefault();
    const id = e.dataTransfer.getData("text/plain");
    e.target.insertAdjacentElement("beforebegin", document.getElementById(id));
  });
</script>

</body>
</html>

このサンプルコードでは、カスタム要素をドラッグ&ドロップできるように実装しています。

カスタム要素がドラッグされた際、その要素のIDをdataTransferオブジェクトに保存し、ドロップされた時にそのIDを元に要素を移動させています。

●応用例4:複数要素の同時ドラッグ&ドロップ

複数の要素を同時にドラッグ&ドロップする機能も実装できます。

下記のサンプルコードでは、複数の要素を同時にドラッグ&ドロップできるようにしています。

○サンプルコード5:複数要素の同時ドラッグ&ドロップ

<!DOCTYPE html>
<html>
<head>
<style>
  .item {
    display: inline-block;
    width: 100px;
    height: 100px;
    background-color: blue;
    margin: 5px;
    cursor: move;
  }
  .selected {
    border: 3px solid red;
  }
</style>
</head>
<body>

<div class="item" draggable="true" id="item1">アイテム1</div>
<div class="item" draggable="true" id="item2">アイテム2</div>
<div class="item" draggable="true" id="item3">アイテム3</div>

<script>
  const items = document.querySelectorAll(".item");
  let selectedItems = [];

  items.forEach((item) => {
    item.addEventListener("mousedown", (e) => {
      if (e.ctrlKey) {
        e.target.classList.toggle("selected");
        if (selectedItems.includes(e.target)) {
          selectedItems = selectedItems.filter((el) => el !== e.target);
        } else {
          selectedItems.push(e.target);
        }
      } else {
        selectedItems = [e.target];
        items.forEach((el) => el.classList.remove("selected"));
        e.target.classList.add("selected");
      }
    });

    item.addEventListener("dragstart", (e) => {
      if (!selectedItems.includes(e.target)) {
        selectedItems = [e.target];
        items.forEach((el) => el.classList.remove("selected"));
        e.target.classList.add("selected");
      }
      e.dataTransfer.setData("text/plain", JSON.stringify(selectedItems.map((el) => el.id)));
    });

    item.addEventListener("dragover", (e) => {
      e.preventDefault();
    });

    item.addEventListener("drop", (e) => {
      e.preventDefault();
      const ids = JSON.parse(e.dataTransfer.getData("text/plain"));
      const targetIndex = Array.from(items).indexOf(e.target);
      ids.forEach((id, index) => {
        const el = document.getElementById(id);
        e.target.parentNode.insertBefore(el, targetIndex > index ? e.target.nextSibling : e.target);
      });
    });
  });
</script>

</body>
</html>

このサンプルコードでは、複数の要素を同時にドラッグ&ドロップできるように実装しています。

この例では、マウスで要素をクリックすることで選択し、選択された要素がドラッグ&ドロップされると、その要素が移動されます。

●応用例5:スナップ機能付きドラッグ&ドロップ

スナップ機能を実装することで、ドラッグ&ドロップした要素が指定した場所に自動的に整列されるようになります。

○サンプルコード6:スナップ機能付きドラッグ&ドロップ

<!DOCTYPE html>
<html>
<head>
<style>
  .container {
    position: relative;
    width: 300px;
    height: 300px;
    background-color: lightgray;
  }
  .item {
    position: absolute;
    width: 50px;
    height: 50px;
    background-color: blue;
    cursor: move;
  }
</style>
</head>
<body>

<div class="container">
  <div class="item" draggable="true" id="item1"></div>
</div>

<script>
  const item = document.getElementById("item1");
  const container = document.querySelector(".container");
  const snapSize = 50;

  item.addEventListener("dragstart", (e) => {
    e.dataTransfer.setDragImage(new Image(), 0, 0);
  });

  container.addEventListener("dragover", (e) => {
    e.preventDefault();
  });

  container.addEventListener("drop", (e) => {
    e.preventDefault();
    const x = Math.round(e.clientX / snapSize) * snapSize;
    const y = Math.round(e.clientY / snapSize) * snapSize;
    item.style.left = x + 'px';
    item.style.top = y + 'px';
  });
</script>

</body>
</html>

このサンプルコードでは、スナップ機能を付けたドラッグ&ドロップを実装しています。

この例では、ドラッグされた要素が指定した場所(グリッド)に自動的に整列するようになっています。

スナップサイズを変更することで、グリッドの大きさを調整できます。

●応用例6:制限付きドラッグ&ドロップ

制限付きドラッグ&ドロップを実装することで、ドラッグ&ドロップが特定のエリア内でのみ行われるように制限できます。

○サンプルコード7:制限付きドラッグ&ドロップ

<!DOCTYPE html>
<html>
<head>
<style>
  .container {
    position: relative;
    width: 300px;
    height: 300px;
    background-color: lightgray;
  }
  .item {
    position: absolute;
    width: 50px;
    height: 50px;
    background-color: blue;
    cursor: move;
  }
</style>
</head>
<body>

<div class="container">
  <div class="item" draggable="true" id="item1"></div>
</div>

<script>
  const item = document.getElementById("item1");
  const container = document.querySelector(".container");

  item.addEventListener("dragstart", (e) => {
    e.dataTransfer.setDragImage(new Image(), 0, 0);
  });

  container.addEventListener("dragover", (e) => {
    e.preventDefault();
  });

  container.addEventListener("drop", (e) => {
    e.preventDefault();
    const rect = container.getBoundingClientRect();
    const x = e.clientX - rect.left - item.clientWidth / 2;
    const y = e.clientY - rect.top - item.clientHeight / 2;
    item.style.left = Math.max(0, Math.min(x, container.clientWidth - item.clientWidth)) + 'px';
    item.style.top = Math.max(0, Math.min(y, container.clientHeight - item.clientHeight)) + 'px';
  });
</script>

</body>
</html>

このサンプルコードでは、ドラッグ&ドロップ操作を特定の範囲内に制限する方法を紹介しています。

この例では、ドラッグされた要素がコンテナの範囲内でのみ移動できるようになっています。

●注意点と対処法

ドラッグ&ドロップ操作にはいくつかの注意点があります。

特に、ブラウザ間での挙動の違いに注意が必要です。

ブラウザによっては、デフォルトのドラッグ&ドロップイベントが期待通りに動作しない場合があります。

そのような状況に対処するために、ブラウザごとの挙動を確認し、必要に応じて適切な処理を行ってください。

●カスタマイズ方法

ここで紹介したサンプルコードは基本的な実装例ですが、応用やカスタマイズも可能です。

例えば、ドラッグ可能な要素やドロップ可能な範囲を動的に変更することができます。

また、他の要素との連携を行ったり、より複雑なロジックを組み込むこともできます。

まとめ

本稿では、ドラッグ&ドロップ機能の基本的な実装方法や応用例をいくつか紹介しました。

それらのサンプルコードを参考に、状況に応じてカスタマイズや拡張が可能です。

ドラッグ&ドロップ機能は、ユーザーが直感的に操作できるため、ウェブアプリケーションのユーザビリティを向上させる効果があります。

ただし、ブラウザ間の挙動の違いや注意点についても理解しておくことが重要です。

適切な対処法を用いることで、さまざまな環境で正しく動作するドラッグ&ドロップ機能を実現できます。

これらの知識を活用し、ユーザーフレンドリーなウェブアプリケーションを開発していくことが望ましいでしょう。

今後も技術の進歩や新たな実装方法が登場することが予想されますので、最新の情報をキャッチアップしておくことも大切です。