【JavaScript】画像をドラッグ&ドロップで読み込む方法|ドロップゾーン・プレビュー・アップロード

画像ファイルをフォームの「参照」ボタンではなく、ドラッグ&ドロップで読み込みたい場面はよくあります。アップロードフォームやプレビュー機能で、ユーザー体験を大きく向上させられます。

HTML5の Drag and Drop API と File API を組み合わせれば、ドロップされた画像ファイルを取得してプレビュー表示・検証・アップロードまで行えます。この記事では最小構成から実用的な実装まで、つまずきやすいポイントとあわせて解説します。

この記事の最重要ポイント:ドロップを受け付けるには dragover イベントで e.preventDefault() を必ず呼ぶ必要があります。これを忘れると drop イベントが発火せず、ブラウザが画像を別タブで開いてしまいます。
スポンサーリンク

ドロップゾーンを作る(基本)

まずドロップを受け付ける領域を用意します。dragover で preventDefault() を呼ぶことで、その要素がドロップ可能になります。

HTML
<div id="dropzone">ここに画像をドロップ</div>
<div id="preview"></div>
ドロップゾーンの基本
const dropzone = document.getElementById("dropzone");

// ★これが必須。dragover で preventDefault しないと drop が発火しない
dropzone.addEventListener("dragover", (e) => {
  e.preventDefault();
});

dropzone.addEventListener("drop", (e) => {
  e.preventDefault(); // ブラウザが画像を開くのを防ぐ
  const files = [...e.dataTransfer.files]; // ドロップされたファイル一覧
  console.log(files); // File オブジェクトの配列
});

ドロップされた画像ファイルを取得する

e.dataTransfer.files に File オブジェクトの一覧が入っています。画像以外も落とせるので、file.type で画像だけに絞り込みます。

画像ファイルだけ抽出
dropzone.addEventListener("drop", (e) => {
  e.preventDefault();

  const files = [...e.dataTransfer.files];
  // MIMEタイプが image/ で始まるものだけ
  const images = files.filter((file) => file.type.startsWith("image/"));

  if (images.length === 0) {
    console.log("画像ファイルではありません");
    return;
  }
  images.forEach(showPreview); // 次のプレビュー関数へ
});

画像をプレビュー表示する

読み込んだ画像を表示するには2つの方法があります。URL.createObjectURL() が高速でメモリ効率も良く推奨です。

URL.createObjectURL でプレビュー(推奨)
const preview = document.getElementById("preview");

function showPreview(file) {
  const img = document.createElement("img");
  img.src = URL.createObjectURL(file); // ファイルから一時URLを生成
  img.style.maxWidth = "200px";

  // 表示後にメモリを解放する
  img.onload = () => URL.revokeObjectURL(img.src);

  preview.appendChild(img);
}
FileReader でプレビュー(data URL)
function showPreviewWithReader(file) {
  const reader = new FileReader();
  reader.onload = (e) => {
    const img = document.createElement("img");
    img.src = e.target.result; // data URL(base64)
    preview.appendChild(img);
  };
  reader.readAsDataURL(file);
}
URL.createObjectURL() は軽量な一時URL(blob:)を作るだけなので大きい画像でも高速です。使い終わったら URL.revokeObjectURL() でメモリを解放しましょう。FileReader はファイル全体をbase64文字列に変換するため、巨大な画像ではメモリを多く使います。

ドラッグ中のハイライトと複数画像対応

ドラッグ中にドロップゾーンの見た目を変えると、操作がわかりやすくなります。dragenter/dragleave でクラスを付け外しします。

ハイライト+複数画像
dropzone.addEventListener("dragenter", (e) => {
  e.preventDefault();
  dropzone.classList.add("dragover");
});
dropzone.addEventListener("dragover", (e) => e.preventDefault());
dropzone.addEventListener("dragleave", () => {
  dropzone.classList.remove("dragover");
});

dropzone.addEventListener("drop", (e) => {
  e.preventDefault();
  dropzone.classList.remove("dragover");

  // 複数ファイルをまとめて処理
  [...e.dataTransfer.files]
    .filter((f) => f.type.startsWith("image/"))
    .forEach(showPreview);
});
ハイライト用CSS
#dropzone {
  border: 2px dashed #94a3b8;
  padding: 2em;
  text-align: center;
}
#dropzone.dragover {
  border-color: #0284c7;
  background: #f0f9ff;
}

形式・サイズを検証する

アップロード前に、許可する画像形式とファイルサイズをチェックします。

画像の検証
function isValidImage(file, maxMB = 5) {
  const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
  const okType = allowedTypes.includes(file.type);
  const okSize = file.size <= maxMB * 1024 * 1024;

  if (!okType) console.log("対応していない形式です:", file.type);
  if (!okSize) console.log("ファイルが大きすぎます:", file.size);

  return okType && okSize;
}

// 使用例:検証を通った画像だけプレビュー
[...e.dataTransfer.files].filter(isValidImage).forEach(showPreview);

サーバーへアップロードする

取得した画像ファイルは FormData に入れて fetch で送信できます。

FormData でアップロード
async function uploadImage(file) {
  const formData = new FormData();
  formData.append("image", file);

  const res = await fetch("/upload", {
    method: "POST",
    body: formData, // Content-Type は自動設定される
  });
  return res.json();
}
ページ全体での誤ドロップに注意:ドロップゾーン以外に画像を落とすと、ブラウザがその画像を開いてしまいます。これを防ぐには window に対しても dragover・drop で e.preventDefault() を設定しておくとよいでしょう。

プレビュー方法の比較

方法 速度・メモリ 用途
URL.createObjectURL() 高速・省メモリ プレビュー全般(推奨)
FileReader(data URL) やや重い base64文字列が必要なとき

よくある質問(FAQ)

Qドロップしても drop イベントが発火しません。
Adragover イベントで e.preventDefault() を呼んでいないことが原因です。ブラウザのデフォルト動作(ファイルを開く)が優先され、drop が発火しません。dragover ハンドラ内で必ず e.preventDefault() を呼んでください。
Qドロップされたファイルはどこで取得できますか?
Adrop イベントの e.dataTransfer.files(FileList)に入っています。[...e.dataTransfer.files] で配列化し、file.type.startsWith("image/") で画像だけに絞り込めます。
QcreateObjectURL と FileReader はどちらを使うべき?
Aプレビュー表示なら URL.createObjectURL() が高速・省メモリで推奨です。使用後は URL.revokeObjectURL() で解放します。base64のdata URLが必要な場合(そのまま保存・送信したい等)は FileReader.readAsDataURL() を使います。
Qスマホ(タッチ)でもドラッグ&ドロップできますか?
AHTML5のDrag and Drop APIはモバイルで動作しないことがあります。スマホ対応が必要なら <input type="file" accept="image/*"> による選択を併用するか、タッチイベント対応のライブラリ(SortableJS 等)を使うのが現実的です。

まとめ

JavaScriptで画像をドラッグ&ドロップで読み込む方法のポイントを整理します。

  • dragover で e.preventDefault() を必ず呼ぶ(忘れると drop が発火しない)
  • ドロップされたファイルは e.dataTransfer.files、画像判定は file.type.startsWith("image/")
  • プレビューは URL.createObjectURL() が推奨(使用後 revokeObjectURL)
  • 形式・サイズを検証してからアップロード(FormData + fetch)
  • ドラッグ中のハイライトや、ページ全体での誤ドロップ防止も入れると親切

関連として、画像が読み込まれたかを判定する方法・画像を遅延読み込みする方法・ファイルのダウンロードボタンを追加する方法もあわせて確認すると、画像・ファイル操作に強くなれます。