JavaScript

最終更新日:
公開日:

レシピ

イベント

ドラッグ・ドロップでファイルを選択する

ローカルファイルをブラウザの特定の位置にドラッグ&ドロップするとファイル選択する機能を実装する方法について解説します。

この記事のポイント

  • ドラッグ&ドロップによるファイル選択はイベントdragenterdragoverdropを使う

ローカルファイルをドラッグ&ドロップしてファイルを選択する

ローカルにあるファイルをブラウザにドラッグ&ドロップしてファイル選択できるようにするときは、ドラッグ&ドロップに関連するイベントdragenterdragoverdropを使います。

今回はこれらのイベントを使って、ドラッグ&ドロップによるファイル選択とあわせて、Ajaxを使ったファイルアップロードの実装について解説していきます。

このようにローカルからファイルがブラウザにドラッグ&ドロップされたことをイベントリスナーで検出して、Ajaxを使ってファイルをアップロードします。
アップロード完了後は以下のように結果を表示します。

今回は以下のコードを使って解説を進めていきます。
HTML、CSS、JavaScriptのコードを1つのファイルにまとめたサンプルページはこちら

HTML コード例

<h1>JavaScriptレシピ</h1>
<p id="text_message"></p>
<section id="dropzone">
  <p>ここにファイルをドロップしてください</p>
</section>

CSS コード例

#dropzone {
  padding: 20px;
  width: 100%;
  height: 200px;
  box-sizing: border-box;
  border: 2px solid #4a93cb;
  background-color: #b7d8f2;
}

#dropzone p {
  margin: 0;
  padding: 0;
}

JavaScript コード例

function uploadFile(file) {
  const upload_uri = "./upload.php";
  const xhr = new XMLHttpRequest();
  const fd = new FormData();
  let p_element = document.getElementById("text_message");
  
  xhr.open("post", upload_uri, true);
  xhr.onreadystatechange = function() {
    if( xhr.readyState === 4 && xhr.status === 200) {

      if( xhr.responseText === 'true' ) {
        p_element.textContent = 'アップロードに成功しました';
      } else {
        p_element.textContent = 'アップロードに失敗しました';
      }
    }
  };
  fd.append('uploadFile', file);
  xhr.send(fd);
}

window.addEventListener('DOMContentLoaded', function(){

  let dropzone = document.getElementById('dropzone');

  dropzone.addEventListener("dragenter", function(e){
    e.stopPropagation();
    e.preventDefault();
  });

  dropzone.addEventListener("dragover", function(e){
    e.stopPropagation();
    e.preventDefault();
  });

  dropzone.addEventListener("drop", function(e){
    e.stopPropagation();
    e.preventDefault();

    const upload_files = e.dataTransfer.files;

    for(let i=0; i<upload_files.length; i++) {
      uploadFile(upload_files[i]);
    }
  });

});

section要素の上にファイルがドロップされたときに、dropを検出してuploadFileメソッドを呼び出します。
uploadFileメソッドでは同じ階層にある「upload.php」に対してファイルデータを送信して、アップロードの成否でページにメッセージを表示します。

ドラッグ&ドロップによるファイル選択は上記のコードで実現できますが、ファイルを受け取る「upload.php」のコードは以下の内容になります。
赤字の箇所にあるmove_uploaded_fileメソッドでサーバーにファイルを保存する処理を実行します。
このメソッドについて詳しくは「アップロードしたファイルを特定のディレクトリに保存する」をご覧ください。

PHP コード例

<?php

if( isset($_FILES['uploadFile']) ) {

  // 画像をアップロード
  if( move_uploaded_file( $_FILES['uploadFile']['tmp_name'], './image/'.$_FILES['uploadFile']['name']) ){
    echo 'true';
  } else {
    echo 'false';
  }
  return;
}

exit;

Note

ここでのファイルのアップロードは必要最低限の処理のみ実行しています。公開する本番環境ではファイルの形式やサイズのバリデーションなど、受け取ったデータの内容を確認する処理が加わります。

ここからは、JavaScriptで実行している内容について解説していきます。
まずはイベントリスナーを登録する「window.addEventListener('DOMContentLoaded',〜」以降の箇所に絞ります。

JavaScript コード例

window.addEventListener('DOMContentLoaded', function(){

  let dropzone = document.getElementById('dropzone');

  dropzone.addEventListener("dragenter", function(e){
    e.stopPropagation();
    e.preventDefault();
  });

  dropzone.addEventListener("dragover", function(e){
    e.stopPropagation();
    e.preventDefault();
  });

  dropzone.addEventListener("drop", function(e){
    e.stopPropagation();
    e.preventDefault();

    const upload_files = e.dataTransfer.files;

    for(let i=0; i<upload_files.length; i++) {
      uploadFile(upload_files[i]);
    }
  });

});

1行目のイベントリスナーに登録している「DOMContentLoaded」はページが読み込まれたタイミングで検出されるイベントで、そのときに内側の処理が実行されます。
他のHTML要素のイベントリスナーを登録したり、初期設定を実行するときによく使います。

内側の処理では、まずファイルのドロップするエリアになるid属性dropzone」のsection要素を取得します。
今回はsection要素を使っていますが、どのHTML要素を使ってもドロップエリアに設定することが可能です。

続いて、先ほど取得したドロップエリアにdragenterdragoverdropのイベントリスナーを登録します。
このうち2つのdragenterdragoverはドロップしたときのdropが実行されるようにするために、ブラウザのデフォルトの挙動を停止させる処理が入ります。

dropについても、ブラウザの画面にファイルを展開するデフォルトの挙動を停止させるためにpreventDefaultメソッドを実行します。
その後に、dataTransfer.filesプロパティからアップロードしたファイル情報を取得して変数upload_filesに入れます。

続くfor文では、アップロードされたファイルの数だけループ処理を繰り返します。
for文の中ではファイルの情報をパラメータに指定してuploadFileメソッドを実行します。

ここからはuploadFileメソッドの解説に進みます。
uploadFileメソッドのコードは以下の箇所になります。

JavaScript コード例

function uploadFile(file) {
  const upload_uri = "./upload.php";
  const xhr = new XMLHttpRequest();
  const fd = new FormData();
  const p_element = document.getElementById("text_message");
  
  xhr.open("post", upload_uri, true);
  xhr.onreadystatechange = function() {
    if( xhr.readyState === 4 && xhr.status === 200) {

      if( xhr.responseText === 'true' ) {
        p_element.textContent = 'アップロードに成功しました';
      } else {
        p_element.textContent = 'アップロードに失敗しました';
      }
    }
  };
  fd.append('uploadFile', file);
  xhr.send(fd);
}

メソッドの最初の4行は変数の初期化を行います。
変数upload_uriはファイルの送信先になるPHPファイル名、xhrXMLHttpRequestオブジェクトfdFormDataオブジェクトp_elementにはメッセージを表示するp要素を取得して代入します。

続いてXMLHttpRequestオブジェクトを使ったAjaxによるファイルデータの送信処理を実行していきます。

openメソッドはリクエストを作成します。
パラメータは前からHTTPリクエストメソッド(GETPOSTPUTDELETE)、送信先のURL、非同期通信(truefalse)を行うかを指定します。

続く以下の箇所では、XMLHttpRequestオブジェクトreadyStateプロパティの値が変更されたら指定した処理を実行します。

JavaScript コード例

xhr.onreadystatechange = function() {
  if( xhr.readyState === 4 && xhr.status === 200) {

    if( xhr.responseText === 'true' ) {
      p_element.textContent = 'アップロードに成功しました';
    } else {
      p_element.textContent = 'アップロードに失敗しました';
    }
  }
};

readyStateプロパティには通信状態の進捗に応じて、以下の表の数値が設定されていきます。

状態解説
0UNSENTopenメソッドを呼び出す前
1OPENEDopenメソッドを呼び出した後
2HEADERS_RECEIVEDsendメソッドを呼び出した後
3LOADINGレスポンスのダウンロード中
4DONE通信完了

if文の「xhr.readyState === 4」では通信が完了したかを確認しています。

条件式にあるもう1つの「xhr.status === 200」ではレスポンスのHTTPステータスコードを確認します。
ここでは正常にレスポンスを受け取った数値「200」であるかを確認します。

xhr.responseTextには「upload.php」からのレスポンスが入っています。
このレスポンスが文字列の「true」だった場合と、そうでない場合でそれぞれ異なるメッセージをp要素に設定します。

続いて、次の2行を実行します。

JavaScript コード例

fd.append('uploadFile', file);
xhr.send(fd);

1行目はFormDataオブジェクトappendメソッドを実行します。
このメソッドは送信するフォームデータをセットする役割があり、ここでは名前を「uploadFile」にしてパラメータで受け取ったファイルデータfileをセットします。
設定した名前はinput要素name属性と同じように、PHPが受け取ったフォームデータを処理するときに使うことができます。

最後にXMLHttpRequestオブジェクトsendメソッドを実行して、openメソッドで指定した送信先にセットしたフォームデータを送信します。

以上がJavaScriptのコードで実行している内容になります。
アップロードが成功するとページに「アップロードに成功しました」と表示され、反対に失敗した場合は「アップロードに失敗しました」と表示されます。

ドラッグ&ドロップによるファイル選択はinput要素の「type="file"」の値に紐づけることはできないため、今回のようにAjaxを使ったアップロードと組み合わせた使い方や、ファイル内容をページにすぐ表示するような使い方が多いです。