ウェブアプリケーションからのファイルの使用

この記事は翻訳が完了していません。 この記事の翻訳にご協力ください

HTML5 から DOM に追加された File API によって、ウェブコンテンツがユーザーにローカルファイルを選択するように指示し、それらのファイルを読み取れるようになりました。この選択は HTML の <input type="file"> 要素か、ドラッグ&ドロップのどちらかを使用することで行うことができます。

File API を拡張機能や他の chrome コードから利用することもできます。この場合、もういくつか知っておきたい機能があります。詳細は Using the DOM File API in chrome code をご覧下さい。

選択されたファイルへのアクセス

この HTML を見てください。

<input type="file" id="input">

File API が、ユーザーが選択したファイルを表す File に含まれている FileList へアクセスすることができるようにします。

ユーザーが1つしかファイルを選択しなかった場合は、リスト中の最初のファイルしか考える必要はありません。

旧来の DOM セレクターを使用して選択されたファイル1つにアクセスするには次のようにします。

const selectedFile = document.getElementById('input').files[0];

change イベントでの選択されたファイルへのアクセス

FileListchange イベントからアクセスすることもできます (ただし必須ではありません)。

<input type="file" id="input" onchange="handleFiles(this.files)">

ユーザーがファイルを選択すると、 handleFiles() 関数が呼び出され、 FileList オブジェクトが渡されますが、これにはこのオブジェクトにはユーザーが選択したファイルを表す File オブジェクトが格納されています。

複数のファイルを選択させたいときは、ただ multiple 属性を input 要素に使用するだけです。

<input type="file" id="input" multiple onchange="handleFiles(this.files)">

この場合、 handleFiles() 関数に渡されるファイルリストには、ユーザが選択した各ファイルに対応する File オブジェクトが格納されています。

change のリスナーの動的な追加

次のように、 EventTarget.addEventListener() を使用して change イベントのリスナーを追加する必要があります。

const inputElement = document.getElementById("input");
inputElement.addEventListener("change", handleFiles, false);
function handleFiles() {
  const fileList = this.files; /* ファイルリストを処理するコードがここに入る */
}

この場合、 handleFiles() 関数自体がイベントハンドラーであり、前の例のようにイベントハンドラーが引数を渡していたのとは異なることに注意してください。

選択されたファイルについての情報の取得

FileList オブジェクトはユーザーが選択したすべてのファイルを DOM リストで提供しており、それぞれのファイルは File オブジェクトとして定義されています。ユーザーがいくつのファイルを選択したかは、ファイルリストの length 属性を確認することで特定できます。

const numFiles = files.length;

個々の File オブジェクトは、リストを配列のようにアクセスすることで取得することができます。

for (let i = 0, numFiles = files.length; i < numFiles; i++) {
  const file = files[i];
  // ...
}

このループは、ファイルリスト中のすべてのファイルに反復処理を行います。

File オブジェクトには3つのプロパティがあり、ファイルに関する有益な情報を得られます。

name
ファイル名 (読み取り専用)。このプロパティはファイル名のみを持ち、パスに関する情報は何も持ちあわせていません。
size
ファイルサイズ (読み取り専用)。64ビット整数でバイト数を表現します。
type
ファイルの MIME タイプ (読み取り専用)。 MIME タイプが決定できないときは空文字列 ("") を返します。

例: ファイルサイズを表示

次のコードは size プロパティを利用する例です。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>File(s) size</title>
<script>
function updateSize() {
  let nBytes = 0,
      oFiles = document.getElementById("uploadInput").files,
      nFiles = oFiles.length;
  for (let nFileId = 0; nFileId < nFiles; nFileId++) {
    nBytes += oFiles[nFileId].size;
  }
  let sOutput = nBytes + " bytes";
  // optional code for multiples approximation
  const aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
  for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
    sOutput = nApprox.toFixed(3) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)";
  }
  // end of optional code
  document.getElementById("fileNum").innerHTML = nFiles;
  document.getElementById("fileSize").innerHTML = sOutput;
}
</script>
</head>

<body onload="updateSize()">
  <form name="uploadForm">
    <div>
      <input id="uploadInput" type="file" name="myFiles" onchange="updateSize();" multiple>
      selected files: <span id="fileNum">0</span>;
      total size: <span id="fileSize">0</span>
    </div>
    <div><input type="submit" value="Send file"></div>
  </form>
</body>
</html>

click() メソッドを使い input 要素を隠す

見た目の悪い <input> 要素を隠し、独自のインターフェイスでファイル選択を開き、ユーザーが選択したファイルを表示することができます。 input 要素のスタイルを display: none とし、その上で click() メソッドを <input> に対して呼び出すことで実現できます。

次のような HTML を考えてみましょう。

<input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<button id="fileSelect">Select some files</button>

click イベントを扱うコードは次のようなものです。

const fileSelect = document.getElementById("fileSelect"),
  fileElem = document.getElementById("fileElem");

fileSelect.addEventListener("click", function (e) {
  if (fileElem) {
    fileElem.click();
  }
}, false);

ファイル選択を開く新しいボタンは、好きなようにスタイル付けできます。

label 要素を使用して隠した file input 要素を起動

To allow opening the file picker without using JavaScript (the click() method), a <label> element can be used. Note that in this case the input element must not be hidden using display: none (nor visibility: hidden), otherwise the label would not be keyboard-accessible. Use the visually-hidden technique instead.

Consider this HTML:

<input type="file" id="fileElem" multiple accept="image/*" class="visually-hidden">
<label for="fileElem">Select some files</label>

and this CSS:

.visually-hidden {
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px, 1px, 1px, 1px);
}

/* Separate rule for compatibility, :focus-within is required on modern Firefox and Chrome */
input.visually-hidden:focus + label {
  outline: thin dotted;
}
input.visually-hidden:focus-within + label {
  outline: thin dotted;
}

There is no need to add JavaScript code to call fileElem.click(). Also in this case you can style the label element as you wish. You need to provide a visual cue for the focus status of the hidden input field on its label, be it an outline as shown above, or background-color or box-shadow. (As of time of writing, Firefox doesn’t show this visual cue for <input type="file"> elements.)

Selecting files using drag and drop

You can also let the user drag and drop files into your web application.

The first step is to establish a drop zone. Exactly what part of your content will accept drops may vary depending on the design of your application, but making an element receive drop events is easy:

let dropbox;

dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);

In this example, we're turning the element with the ID dropbox into our drop zone. This is done by adding listeners for the dragenter, dragover, and drop events.

We don't actually need to do anything with the dragenter and dragover events in our case, so these functions are both simple. They just stop propagation of the event and prevent the default action from occurring:

function dragenter(e) {
  e.stopPropagation();
  e.preventDefault();
}

function dragover(e) {
  e.stopPropagation();
  e.preventDefault();
} 

The real magic happens in the drop() function:

function drop(e) {
  e.stopPropagation();
  e.preventDefault();

  const dt = e.dataTransfer;
  const files = dt.files;

  handleFiles(files);
}

Here, we retrieve the dataTransfer field from the event, pull the file list out of it, and then pass that to handleFiles(). From this point on, handling the files is the same whether the user used the input element or drag and drop.

例: ユーザが選択した画像のサムネイルを表示

あなたは写真共有サイトをつくっており、ユーザ-がアップロードする写真を選んでいるときに、そのサムネイル一覧をアップロードすることなしにプレビューさせたいとします。これを実現するのも簡単で、 input 要素もしくはドロップ領域を用意してから、次のような handleFiles() 関数を呼べば良いのです。

function handleFiles(files) {
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    
    if (!file.type.startsWith('image/')){ continue }
    
    const img = document.createElement("img");
    img.classList.add("obj");
    img.file = file;
    preview.appendChild(img); // Assuming that "preview" is the div output where the content will be displayed.
    
    const reader = new FileReader();
    reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
    reader.readAsDataURL(file);
  }
}

Here our loop handling the user-selected files looks at each file's type attribute to see if its MIME type begins with the string "image/"). For each file that is an image, we create a new img element. CSS can be used to establish any pretty borders or shadows and to specify the size of the image, so that doesn't need to be done here.

Each image has the CSS class obj added to it, making it easy to find in the DOM tree. We also add a file attribute to each image specifying the File for the image; this will let us fetch the images for actual upload later. We use Node.appendChild() to add the new thumbnail to the preview area of our document.

Next, we establish the FileReader to handle asynchronously loading the image and attaching it to the img element. After creating the new FileReader object, we set up its onload function and then call readAsDataURL() to start the read operation in the background. When the entire contents of the image file are loaded, they are converted into a data: URL which is passed to the onload callback. Our implementation of this routine sets the img element's src attribute to the loaded image which results in the image appearing in the thumbnail on the user's screen.

オブジェクト URL を利用する

The DOM URL.createObjectURL() and URL.revokeObjectURL() methods let you create simple URL strings that can be used to reference any data that can be referred to using a DOM File object, including local files on the user's computer.

HTML ファイルから URL で参照したい File オブジェクトがある場合、そのオブジェクト URL は次のように作成できます。

const objectURL = window.URL.createObjectURL(fileObj);

オブジェクト URL は File オブジェクトを識別する文字列です。window.URL.createObjectURL() メソッドを呼び出すたびに、一意なオブジェクト URL が生成されます。これはたとえ既に同じファイルについてオブジェクト URL を生成していたとしてもです。オブジェクト URL はドキュメントが解放された際に自動的に解放されますが、もしあなたのページが動的にオブジェクト URL を扱う場合は、window.URL.revokeObjectURL() メソッドを使い明示的に開放する方がよいでしょう。

URL.revokeObjectURL(objectURL);

例: オブジェクト URL で画像を表示

This example uses object URLs to display image thumbnails. In addition, it displays other file information including their names and sizes.

インターフェースとなる HTML は次のようになります。

<input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<a href="#" id="fileSelect">Select some files</a> 
<div id="fileList">
  <p>No files selected!</p>
</div>

This establishes our file <input> element as well as a link that invokes the file picker (since we keep the file input hidden to prevent that less-than-attractive user interface from being displayed). This is explained in the section Using hidden file input elements using the click() method, as is the method that invokes the file picker.

handleFiles()メソッドはこんな風になります。

const fileSelect = document.getElementById("fileSelect"),
    fileElem = document.getElementById("fileElem"),
    fileList = document.getElementById("fileList");

fileSelect.addEventListener("click", function (e) {
  if (fileElem) {
    fileElem.click();
  }
  e.preventDefault(); // prevent navigation to "#"
}, false);

function handleFiles(files) {
  if (!files.length) {
    fileList.innerHTML = "<p>No files selected!</p>";
  } else {
    fileList.innerHTML = "";
    const list = document.createElement("ul");
    fileList.appendChild(list);
    for (let i = 0; i < files.length; i++) {
      const li = document.createElement("li");
      list.appendChild(li);
      
      const img = document.createElement("img");
      img.src = URL.createObjectURL(files[i]);
      img.height = 60;
      img.onload = function() {
        URL.revokeObjectURL(this.src);
      }
      li.appendChild(img);
      const info = document.createElement("span");
      info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
      li.appendChild(info);
    }
  }
}

This starts by fetching the URL of the <div> with the ID fileList. This is the block into which we'll insert our file list, including thumbnails.

If the FileList object passed to handleFiles() is null, we simply set the inner HTML of the block to display "No files selected!". Otherwise, we start building our file list, as follows:

  1. 新しく <ul> 要素を作成する
  2. The new list element is inserted into the <div> block by calling its Node.appendChild() method.
  3. FileList オブジェクト (files) 中の各 File オブジェクトについて以下を実行する
    1. 新しく <li> 要素を生成し、リストに追加する
    2. 新しく <img> 要素を生成する
    3. window.URL.createObjectURL() を使用して blob の URL を作成し、画像のソースにファイルを表す新しいオブジェクト URL を設定する
    4. 画像の高さを60ピクセルに指定する
    5. 画像が読み込まれたらオブジェクト URL は必要なくなるので、画像の load イベントハンドラーを設定して解放するようにする。これは window.URL.revokeObjectURL() メソッドを呼び出し、 img.src で与えたオブジェクトを渡すことで実現する
    6. 新しいリスト項目をリストに追加する

Here is a live demo of the code above:

例: ユーザが選択したファイルを送信

ひとつ前の画像サムネイルの例のように、ユーザーが選択したファイルをサーバーへ送信したい場合もあるでしょう。これもとても簡単に、そして非同期に行うことができます。

アップロードタスクの生成

では、さきほどのサムネイルを生成する例を拡張しましょう。さきほどの例では、サムネイル画像には obj という class がつけられており、またそれぞれの File オブジェクトは file という属性につけられていました。これにより、ユーザがアップロードのため選択した画像を得るのはとても簡単です。 document.querySelectorAll() を使うと、次のように書けます。

function sendFiles() {
  const imgs = document.querySelectorAll(".obj");
  
  for (let i = 0; i < imgs.length; i++) {
    new FileUpload(imgs[i], imgs[i].file);
  }
}

2行目で、imgs という変数に、obj という class がつけられた要素のリストを格納しています。この場合、要素はすべて画像サムネイルになります。要素のリストを得られたら後は簡単です。リストを見ていき、ひとつのアイテムに対し FileUpload インスタンスを生成すれば良いのです。それぞれのハンドラが該当するファイルをアップロードします。

ファイルのアップロードプロセス処理

FileUpload 関数は2つの引数を取ります。1番目は img 要素で、2番目が画像データを読むファイルになります。

function FileUpload(img, file) {
  const reader = new FileReader();  
  this.ctrl = createThrobber(img);
  const xhr = new XMLHttpRequest();
  this.xhr = xhr;
  
  const self = this;
  this.xhr.upload.addEventListener("progress", function(e) {
        if (e.lengthComputable) {
          const percentage = Math.round((e.loaded * 100) / e.total);
          self.ctrl.update(percentage);
        }
      }, false);
  
  xhr.upload.addEventListener("load", function(e){
          self.ctrl.update(100);
          const canvas = self.ctrl.ctx.canvas;
          canvas.parentNode.removeChild(canvas);
      }, false);
  xhr.open("POST", "http://demos.hacks.mozilla.org/paul/demos/resources/webservices/devnull.php");
  xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
  reader.onload = function(evt) {
    xhr.send(evt.target.result);
  };
  reader.readAsBinaryString(file);
}

FileUpload() 関数は throbber という、進行状況を表示するものを作ります。その後、 XMLHttpRequest でデータをアップロードします。

データを実際に転送する前には、次のようなステップが実行されています。

  1. XMLHttpRequest のアップロード progress リスナーが登録され、throbber に新しいパーセント値を設定します。こうすることでアップロードが進行しても、throbber が最新の状態を反映します
  2. XMLHttpRequest のアップロード load イベントハンドラーが登録され、throbber を100%に更新します (これは進行状況の表示がちゃんと100%になるように見せるためです)。そして、もう必要がなくなった throbber を削除します。つまりアップロードが終わると、 throbber が消えるということです
  3. 画像ファイルのアップロードリクエストは XMLHttpRequestopen() メソッドを呼び出し、POST リクエストを開始させます
  4. アップロードの MIME タイプは XMLHttpRequestoverrideMimeType() メソッドで設定します。この場合は一般的な MIME タイプを設定しています。何をするかによりますが、MIME タイプを指定しなくてもいい場合もあります
  5. ファイルをバイナリ文字列に変換するため、FileReader オブジェクトを使用します
  6. 最後に、内容が読み込まれたら XMLHttpRequestsendAsBinary() メソッドが呼び出され、ファイルをアップロードします

ファイルのアップロード処理を非同期に扱う

This example, which uses PHP on the server side and JavaScript on the client side, demonstrates asynchronous uploading of a file.

<?php
if (isset($_FILES['myFile'])) {
    // Example:
    move_uploaded_file($_FILES['myFile']['tmp_name'], "uploads/" . $_FILES['myFile']['name']);
    exit;
}
?><!DOCTYPE html>
<html>
<head>
    <title>dnd binary upload</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="application/javascript">
        function sendFile(file) {
            const uri = "/index.php";
            const xhr = new XMLHttpRequest();
            const fd = new FormData();
            
            xhr.open("POST", uri, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    alert(xhr.responseText); // handle response.
                }
            };
            fd.append('myFile', file);
            // Initiate a multipart/form-data upload
            xhr.send(fd);
        }

        window.onload = function() {
            const dropzone = document.getElementById("dropzone");
            dropzone.ondragover = dropzone.ondragenter = function(event) {
                event.stopPropagation();
                event.preventDefault();
            }
    
            dropzone.ondrop = function(event) {
                event.stopPropagation();
                event.preventDefault();

                const filesArray = event.dataTransfer.files;
                for (let i=0; i<filesArray.length; i++) {
                    sendFile(filesArray[i]);
                }
            }
        }
    </script>
</head>
<body>
    <div>
        <div id="dropzone" style="margin:30px; width:500px; height:300px; border:1px dotted grey;">Drag & drop your file here...</div>
    </div>
</body>
</html>

例: オブジェクト URL を使用して PDF を表示

Object URLs can be used for other things than just images! They can be used to display embedded PDF files or any other resources that can be displayed by the browser.

In Firefox, to have the PDF appear embedded in the iframe (rather than proposed as a downloaded file), the preference pdfjs.disabled must be set to false .

<iframe id="viewer">

And here is the change of the src attribute:

const obj_url = URL.createObjectURL(blob);
const iframe = document.getElementById('viewer');
iframe.setAttribute('src', obj_url);
URL.revokeObjectURL(obj_url);

例: 他のファイル形式でのオブジェクト URL の使用

You can manipulate files of other formats the same way. Here is how to preview uploaded video:

const video = document.getElementById('video');
const obj_url = URL.createObjectURL(blob);
video.src = obj_url;
video.play();
URL.revokeObjectURL(obj_url);

仕様書

仕様書 状態 備考
HTML Living Standard
File upload state の定義
現行の標準
File API 草案 初回定義

関連情報