追加してファイルをアップロード

2024/03/30に公開

ファイルを追加してアップロードできるようにjavascriptで処理します。

通常

普通に書いた場合、一度アップロードすると追加でファイルアップロードできず
上書きされます。

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

これだと一度に複数選択できるが、連続してアップロードするとやはり上書きされます。

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

やりたいこと

複数回連続してアップロードできるように
イベントリスナーを使ってファイルアップロードされたタイミングで
ファイル追加・更新できるようにします。

  <script>
    // 1. アップロードしたファイルを保持する変数を定義
    const fileList = [];
    // 2. イベントリスナーを定義
    const upload = document.getElementById("test");
    upload.addEventListener("change", (e) => {
      const dt = new DataTransfer();
      const uploaded = e.target.files;
      // 3. アップロードしたファイルをいったん移築
      fileList.forEach((file) => {
        dt.items.add(file);
      })
      for (let i=0;i<uploaded.length;i++) {
        dt.items.add(uploaded[i]);
        // 4. アップロードしたファイルを保持
        fileList.push(uploaded[i]);
      }
      // 5. もとにもどす
      upload.files = dt.files;
    })
  </script>

連続してアップロードできるようにするため、
ドラッグアンドドロップで利用するDataTransfer()を使います。
アップロードしたファイルを操作することができます。
https://developer.mozilla.org/ja/docs/Web/API/DataTransfer

アップロードはFileListでイベントで取得できますが、
配列のようにforEachは利用できません。
Array.fromを使うとforEachが使えます。

Array.from(uploaded).forEach(file => {
  // 以下処理
})

html標準だと、1ファイルしかファイル名が表示されないため、
すべてのファイル名を表示するためには
カスタムする必要があります。

    const names = document.getElementById("names");
    const name = document.createElement("span");
    upload.addEventListener("change", (e) => {
      // 6. ファイル名を表示
      name.textContent = fileList.map((file) => { return file.name }).join();
      names.appendChild(name);
    }

そのままだと
ファイルアップロードとフォントが違うので
inputタグを透明化して自作するとデザインが統一されてよさげです。

完成

複数アップロード後の画面キャプチャ

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    *,
    *::before,
    *::after {
      box-sizing: border-box;
    }

    :root {
      --border_rgb_color: 206, 212, 218;
    }

    label {
      position: relative;
      display: inline-block;
      padding: .4em 1em;
      margin: 0 .5em 0 0;
      border: 1px solid rgb(var(--border_rgb_color));
      border-radius: .25em;
      height: max-content;
    }

    input[type="file"] {
      position: absolute;
      opacity: 0;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
    }

    .upload_block {
      display: flex;
      align-items: center;
    }

    .file_names span {
      font-size: .80em;
    }
  </style>
  <title></title>
</head>

<body>
  <div class="upload_block">
    <label>
      <input type="file" name="" id="test" multiple>
      <span>ファイルを選択</span>
    </label>
    <div id="names" class="file_names"></div>
  </div>
  <script>
    const fileList = [];
    const upload = document.getElementById("test");
    const names = document.getElementById("names");
    const name = document.createElement("span");
    upload.addEventListener("change", (e) => {
      const dt = new DataTransfer();
      const uploaded = e.target.files;
      fileList.forEach((file) => {
        dt.items.add(file);
      })
      for (let i = 0; i < uploaded.length; i++) {
        dt.items.add(uploaded[i]);
        fileList.push(uploaded[i]);
      }
      name.textContent = fileList.map((file) => { return file.name }).join();
      names.appendChild(name);
      upload.files = dt.files;
    })
  </script>
</body>

</html>

まとめ

ファイル名表示は素のhtmlとjavascriptで書くより、フレームワーク使ったほうが
簡単だなと思いました。
アップロードしたファイルの一部削除もこれなら制御できるようになります。

Discussion