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

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

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

この HTML を考えてください。


```html
<input type="file" id="input" multiple />
```


ファイル API により、ユーザーが選択したファイルを表す `File` オブジェクトを含む `FileList` にアクセスすることができます。

`multiple` 属性を `input` 要素に付けることで、ユーザーが複数のファイルを選択することができるようになります。

旧来の DOM セレクターを使って、最初に選択されたファイルにアクセスします。


```js
const selectedFile = document.getElementById("input").files[0];
```

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

`change` イベントを通して `FileList` にアクセスすることもできます (ただし必須ではありません)。このように `EventTarget.addEventListener()` を使って `change` イベントのリスナーを追加する必要があります。


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

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

DOM が提供する `FileList` オブジェクトは、`File` オブジェクトとして指定された、ユーザーが選択したすべてのファイルをリストアップします。ファイルリストの `length` 属性の値をチェックすることで、ユーザーが選択したファイルの数を知ることができます。


```js
const numFiles = fileList.length;
```


個々の `File` オブジェクトは、単に配列としてリストにアクセスするだけで取得できます。


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


このループは、ファイルリスト内のすべてのファイルを繰り返し処理します。

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

`name`

読み取り専用の文字列としてのファイル名。これはファイル名のみで、パスに関する情報は含まれていません。

`size`

読み取り専用の 64 ビット整数によるバイト単位のファイルサイズです。

`type`

読み取り専用の文字列としてのファイルの MIME タイプです。MIME タイプが特定できないときは空文字列 (`""`) となります。

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

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


```html
<!doctype html>
<html lang="ja-JP">
  <head>
    <meta charset="UTF-8" />
    <title>ファイルのサイズ</title>
  </head>

  <body>
    <form name="uploadForm">
      <div>
        <input id="uploadInput" type="file" multiple />
        <label for="fileNum">選択されたファイル:</label>
        <output id="fileNum">0</output>;
        <label for="fileSize">合計サイズ:</label>
        <output id="fileSize">0</output>
      </div>
      <div><input type="submit" value="Send file" /></div>
    </form>

    <script>
 const uploadInput = document.getElementById("uploadInput");
 uploadInput.addEventListener(
 "change",
 () => {
 // 合計サイズを計算
 let numberOfBytes = 0;
 for (const file of uploadInput.files) {
 numberOfBytes += file.size;
 }

 // 最も近い接頭辞単位に近似
 const units = [
 "B",
 "KiB",
 "MiB",
 "GiB",
 "TiB",
 "PiB",
 "EiB",
 "ZiB",
 "YiB",
 ];
 const exponent = Math.min(
 Math.floor(Math.log(numberOfBytes) / Math.log(1024)),
 units.length - 1,
 );
 const approx = numberOfBytes / 1024 \*\* exponent;
 const output =
 exponent === 0
 ? `${numberOfBytes} bytes`
 : `${approx.toFixed(3)} ${
 units[exponent]
 } (${numberOfBytes} bytes)`;

 document.getElementById("fileNum").textContent =
 uploadInput.files.length;
 document.getElementById("fileSize").textContent = output;
 },
 false,
 );
 </script>
  </body>
</html>
```

click() メソッドを使用して非表示の input 要素を使用する
-----------------------------------

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

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


```html
<input
 type="file"
 id="fileElem"
 multiple
 accept="image/\*"
 style="display:none" />
<button id="fileSelect" type="button">
  いくつかのファイルを選択してください。
</button>
```


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


```js
const fileSelect = document.getElementById("fileSelect");
const fileElem = document.getElementById("fileElem");

fileSelect.addEventListener(
  "click",
  (e) => {
    if (fileElem) {
      fileElem.click();
    }
  },
  false,
);
```


`<button>` は、好きなようにスタイル付けできます。

label 要素を使用して非表示の file input 要素を起動
----------------------------------

JavaScript (click() メソッド) を使用せずにファイル選択を開けるようにするために、 `<label>` 要素を使用します。この場合、 input 要素に `display: none` (または `visibility: hidden`) を設定して非表示に設定すると、ラベルがキーボードからアクセスできなくなります。代わりに、視覚的に非表示にする手法 (visually-hidden technique) を使用します。

次の HTML を見てください。


```html
<input
 type="file"
 id="fileElem"
 multiple
 accept="image/\*"
 class="visually-hidden" />
<label for="fileElem">いくつかのファイルを選択してください。</label>
```


そしてこの CSS です。


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

input.visually-hidden:is(:focus, :focus-within) + label {
  outline: thin dotted;
}
```


JavaScript コードを追加して `fileElem.click()` を呼び出す必要はありません。またこの場合は、ラベル要素のスタイルを希望どおりに設定することもできます。前例のようにアウトラインに設定したり、background-color や box-shadow を設定したりして、ラベルの非表示入力フィールドのフォーカスステータスを視覚的に示す必要があります。(この記事を書いている時点では、 Firefox は `<input type="file">` 要素に対してこの視覚的な手がかりを表示していません。)

ドラッグ & ドロップを使用したファイルの選択
-----------------------

ユーザーがファイルをウェブアプリケーションにドラッグ & ドロップすることもできます。

最初のステップは、ドロップゾーンを確立することです。コンテンツのどの部分がドロップを受け入れるかは、アプリケーションの設計によって異なりますが、要素がドロップイベントを受け取れるようにするのは簡単です。


```js
let dropbox;

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


この例では、ID `dropbox` を持つ要素をドロップゾーンに指定しています。これは、`dragenter`、`dragover`、`drop` の各イベントのリスナーを追加することで行われます。

実際には、この場合、 `dragenter` と `dragover` のイベントでは何もする必要はありませんので、これらの関数はどちらも簡単です。これらの関数はイベントの伝播を停止し、既定のアクションが発生しないようにするだけです。


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

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


本当の魔法は `drop()` 関数の中で起こります。


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

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

  handleFiles(files);
}
```


ここでは、イベントから `dataTransfer` フィールドを取得し、そこからファイルリストを取得し、それを `handleFiles()` に渡します。これより先は、ユーザーが入力要素を使用したかドラッグ & ドロップを使用するかどうかにかかわらず、ファイルの処理方法は全く同じです。

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

次の素晴らしい写真共有サイトを開発していて、ユーザーが実際に画像をアップロードする前に HTML を使って画像のサムネイルプレビューを表示させたいとしましょう。前に説明したように `input` 要素やドロップゾーンを設定し、次の `handleFiles()` のような関数を呼び出せば良いのです。


```js
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); // 「プレビュー」とは、コンテンツが表示される div 出力のことを想定しています。

    const reader = new FileReader();
    reader.onload = (e) => {
      img.src = e.target.result;
    };
    reader.readAsDataURL(file);
  }
}
```


ここでは、ユーザーが選択したファイルを処理するループが各ファイルの `type` 属性を見て、その MIME タイプが "`image/`" という文字列で始まるかどうかを確認しています。画像である各ファイルに対して、新しい `img` 要素を作成します。CSS は、きれいな境界線や影を設定したり、画像のサイズを指定したりするために使用しますので、ここでは必要ありません。

各画像には CSS クラス `obj` が追加されており、DOM ツリーで簡単に見つけることができます。また、各画像に `file` 属性を追加し、画像の `File` を指定しています。これにより、後で実際にアップロードする画像を取得することができます。`Node.appendChild()` を使用して、文書のプレビュー領域に新しいサムネイルを追加します。

次に、画像の読み込みと `img` 要素へのアタッチを非同期で処理するための `FileReader` を確立します。新しい `FileReader` オブジェクトを作成した後、その `onload` 関数を設定し、`readAsDataURL()` を呼び出してバックグラウンドで読み込み処理を開始します。画像ファイルのコンテンツ全体が読み込まれると、それらは `data:` URL に変換され、`onload` コールバックに渡されます。このルーチンの実装では、`img` 要素の `src` 属性が読み込まれた画像に設定され、その結果、画像がユーザの画面のサムネイルに表示されます。

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

DOM の `URL.createObjectURL()` と `URL.revokeObjectURL()` メソッドを使用すると、ユーザーのコンピューター上のローカルファイルなど、DOM `File` オブジェクトを使用して参照可能なあらゆるデータを参照するために使用できるシンプルな URL 文字列を作成できます。

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


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


オブジェクト URL は `File` オブジェクトを識別する文字列です。 `URL.createObjectURL()` を呼び出すたびに、すでにそのファイルのオブジェクト URL を作成していても、一意のオブジェクト URL が作成されます。これらはそれぞれ解除する必要があります。これらはドキュメントがアンロードされると自動的に解放されますが、ページが動的にこれらを使用している場合は `URL.revokeObjectURL()` を呼び出して明示的に解放する必要があります。


```js
URL.revokeObjectURL(objectURL);
```

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

この例では、オブジェクト URL を使用して画像のサムネイルを表示しています。さらに、ファイル名やサイズなどの他のファイル情報も表示します。

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


```html
<input
 type="file"
 id="fileElem"
 multiple
 accept="image/\*"
 style="display:none" />
<a href="#" id="fileSelect">いくつかのファイルを選択してください。</a>
<div id="fileList">
  <p>ファイルが選択されていません。</p>
</div>
```


これにより、ファイル `<input>` 要素と、ファイル選択を呼び出すリンクが確立されます (あまり美しくないファイル入力を非表示にするため)。これは、ファイル選択を呼び出すメソッドと同様に、click() メソッドを使用して非表示の input 要素を使用するの節で説明されています。

`handleFiles()` メソッドは次のようになります。


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

fileSelect.addEventListener(
  "click",
  (e) => {
    if (fileElem) {
      fileElem.click();
    }
    e.preventDefault(); // "#" への移動を防ぐ
  },
  false,
);

fileElem.addEventListener("change", handleFiles, false);

function handleFiles() {
  if (!this.files.length) {
    fileList.innerHTML = "<p>ファイルが選択されていません。</p>";
  } else {
    fileList.innerHTML = "";
    const list = document.createElement("ul");
    fileList.appendChild(list);
    for (let i = 0; i < this.files.length; i++) {
      const li = document.createElement("li");
      list.appendChild(li);

      const img = document.createElement("img");
      img.src = URL.createObjectURL(this.files[i]);
      img.height = 60;
      img.onload = () => {
        URL.revokeObjectURL(img.src);
      };
      li.appendChild(img);
      const info = document.createElement("span");
      info.innerHTML = `${this.files[i].name}: ${this.files[i].size} バイト`;
      li.appendChild(info);
    }
  }
}
```


これは、 `<div>` の URL を `fileList` という ID で取得することから始まります。これは、サムネイルを含むファイルリストを挿入するブロックです。

`handleFiles()` に渡された `FileList` オブジェクトが `null` の場合、ブロックの内部 HTML に「ファイルが選択されていません」と表示するように設定します。そうでない場合は、次のようにファイルリストの構築を開始します。

1. 新しく順序なしリスト (`<ul>`) 要素を作成します。
2. 新しいリスト要素は、`<div>` ブロックの中に `Node.appendChild()` メソッドを呼び出すことで挿入されます。
3. `files` で表される `FileList` 内のそれぞれの `File` に対して次の処理を実行します。
    1. 新しくリスト項目 (`<li>`) 要素を作成し、リストに挿入します。
    2. 新しく画像 (`<img>`) 要素を作成します。
    3. `URL.createObjectURL()` を用いて、Blob の URL を作成して、画像のソースをファイルを表す新しいオブジェクト URL に設定します。
    4. 画像の高さを 60 ピクセルに設定します。
    5. 画像が読み込まれると不要になるため、画像の読み込みイベントハンドラーを設定してオブジェクトの URL を解放します。これは `URL.revokeObjectURL()` メソッドを呼び出し、`img.src` で指定したオブジェクト URL 文字列を渡すことで行います。
    6. 新しいリスト項目をリストに追加する。

上のコードのライブデモはこちらです。

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

もう１つは、ユーザーが選択したファイルやファイル (先ほどの例で選択した画像など) をサーバーにアップロードできるようにすることです。これは非常に簡単に非同期で行うことができます。

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

前の例でサムネイルを作成したコードの続きで、すべてのサムネイル画像が CSS クラス `obj` にあり、対応する `File` が `file` 属性に添付されていることを思い出してください。これにより、このように`Document.querySelectorAll()`を使用して、ユーザーがアップロードするために選択した画像をすべて選択することができます。


```js
function sendFiles() {
  const imgs = document.querySelectorAll(".obj");

  for (let i = 0; i < imgs.length; i++) {
    new FileUpload(imgs[i], imgs[i].file);
  }
}
```


2 行目は、CSS クラス `obj` を持つドキュメント内のすべての要素の `NodeList` を取得し `imgs` と呼ばれる変数に格納します。この例では、これらの要素はすべての画像サムネイルになります。このリストを取得したら、それを参照して、それぞれの新しい `FileUpload` インスタンスを作成するのは簡単です。それぞれが対応するファイルのアップロードを処理します。

### ファイルのアップロード処理を行う

`FileUpload` 関数は 2 つの入力、画像要素と画像データを読み込むファイルを受け付けます。


```js
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",
    (e) => {
      if (e.lengthComputable) {
        const percentage = Math.round((e.loaded \* 100) / e.total);
        self.ctrl.update(percentage);
      }
    },
    false,
  );

  xhr.upload.addEventListener(
    "load",
    (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 = (evt) => {
    xhr.send(evt.target.result);
  };
  reader.readAsBinaryString(file);
}

function createThrobber(img) {
  const throbberWidth = 64;
  const throbberHeight = 6;
  const throbber = document.createElement("canvas");
  throbber.classList.add("upload-progress");
  throbber.setAttribute("width", throbberWidth);
  throbber.setAttribute("height", throbberHeight);
  img.parentNode.appendChild(throbber);
  throbber.ctx = throbber.getContext("2d");
  throbber.ctx.fillStyle = "orange";
  throbber.update = (percent) => {
    throbber.ctx.fillRect(
      0,
      0,
      (throbberWidth \* percent) / 100,
      throbberHeight,
    );
    if (percent === 100) {
      throbber.ctx.fillStyle = "green";
    }
  };
  throbber.update(0);
  return throbber;
}
```


上の `FileUpload()` 関数は、進捗情報を表示するための throbber を作成し、データのアップロードを処理するための `XMLHttpRequest` を作成します。

実際にデータを転送する前に、いくつかの準備段階があります。

1. `XMLHttpRequest` のアップロード `progress` リスナーは、アップロードの進捗に応じて最新の情報に基づいて throbber が更新されるように、新しいパーセント値の情報で throbber を更新するように設定されています。
2. `XMLHttpRequest` のアップロード `load` イベントハンドラーは、進捗インジケーターが実際に 100 % に達することを確認するために、throbber の進捗情報を 100% に更新するように設定されています (プロセス中に粒度のクセがある場合)。そして、必要がなくなれば throbber を削除します。これにより、アップロードが完了すると throbber が消えます。
3. 画像ファイルをアップロードするリクエストは、 `XMLHttpRequest` の `open()` メソッドを呼び出して POST リクエストを生成することで開始されます。
4. アップロードの MIME タイプは `XMLHttpRequest` 関数の `overrideMimeType()` を呼び出して設定します。この場合、一般的な MIME タイプを使用しています。用途によっては MIME タイプを設定する必要がない場合もあります。
5. `FileReader` オブジェクトを使用して、ファイルをバイナリー文字列に変換します
6. 最後に、コンテンツがロードされると、 `XMLHttpRequest` 関数の `send()` が呼び出され、ファイルのコンテンツがアップロードされます。
### ファイルのアップロード処理を非同期に扱う

この例では、サーバー側で PHP を使用し、クライアント側で JavaScript を使用して、ファイルの非同期アップロードを実演しています。


```php
<?php
if (isset($\_FILES['myFile'])) {
 // 例:
 move\_uploaded\_file($\_FILES['myFile']['tmp\_name'], "uploads/" . $\_FILES['myFile']['name']);
 exit;
}
?><!DOCTYPE html>
<html lang="ja-JP">
<head>
  <meta charset="UTF-8">
  <title>dnd binary upload</title>
    <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 = () => {
 if (xhr.readyState === 4 && xhr.status === 200) {
 alert(xhr.responseText); // handle response.
 }
 };
 fd.append('myFile', file);
 // multipart/form-data のアップロードを開始します。
 xhr.send(fd);
 }

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

 dropzone.ondrop = (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;">ここにファイルをドラッグ & ドロップしてください</div>
    </div>
</body>
</html>
```

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

オブジェクト URL は画像以外にも使用できます。埋め込まれた PDF ファイルや、ブラウザーで表示可能な他のリソースを表示するために使用できます。

Firefox では、 PDF が iframe 内に埋め込まれて表示されるようにするには (ダウンロードファイルとして提案されるのではなく)、`pdfjs.disabled` の設定を `false` 
Non-standard
 に設定する必要があります。


```html
<iframe id="viewer"></iframe>
```


そして、`src` 属性の変更点はこちらです。


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

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

他の形式のファイルも同じように操作できます。ここでは、アップロードされた動画をプレビューする方法を紹介します。


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

関連情報
----

* `File`
* `FileList`
* `FileReader`
* `URL`
* `XMLHttpRequest`
* XMLHttpRequest の使用