背景
ファイルアップロードのUIを触っていて、<input type="file"> から取れる files の中身が配列っぽく見えるのに配列メソッドが使えなくてハマった。この files の正体である FileList と、その制約を回避するための DataTransfer について整理しておく
FileList
FileList は HTML の <input> 要素の files プロパティが返すオブジェクトで、選択されたファイルの一覧を表す。
<input type="file" id="fileInput" multiple>
(ここにファイル選択UIのスクリーンショット)
このinputで選択したファイルの一覧が FileList として返ってくる。
FileList {0: File, 1: File, 2: File, length: 3}
0: File { name: 'dev.jpg', size: 59665, lastModified: 1764643177551, ... }
1: File { name: 'football.jpg', size: 1012021, lastModified: 1764642516495, ... }
2: File { name: 'sample-gif.webp', size: 471912, lastModified: 1764656221369, ... }
length: 3
[[Prototype]]: FileList
できること
-
lengthでファイル件数を取得できる -
files[0],files[1]のようにインデックスで指定のファイルを参照できる(※ ただし配列ではない)
できないこと
- 配列ではないので
push(),pop()などの配列メソッドは使えない
input.files.push is not a function
at HTMLInputElement.<anonymous>
FileList は「配列のように見えるが配列ではない」オブジェクト(Array-likeなオブジェクト)だと押さえておくとよい。
DataTransfer
DataTransfer は、コンテキスト間で転送されるデータ(ドラッグ&ドロップ操作やクリップボードの読み書きなど)を保持するためのオブジェクト。データを一時的に入れて運ぶ「箱」のような役割をする。
主な用途は次の2つ。
- ドラッグ&ドロップ: デスクトップの画像ファイルをWeb画面上にマウスで引っ張ってきて(ドラッグ)、離して(ドロップ)アップロードする時
- コピー&ペースト: ある場所でテキストや画像をコピーして、別の入力欄に貼り付ける時
FileListの制約をDataTransferで回避する
FileList 単体ではできなかった「ファイルの追加・削除」が、DataTransfer を経由するとできる。DataTransfer の items に対して add() でファイルを積み、その結果できあがる dt.files(これも FileList)を input.files に代入し直す、という流れになる。
<input type="file" id="fileInput" multiple>
<script>
document.getElementById('fileInput').addEventListener('change', function () {
const input = this
console.log('--- 選択直後の FileList ---')
console.log('件数:', input.files.length)
for (let i = 0; i < input.files.length; i++) {
console.log(i + '番目:', input.files[i].name)
}
console.log('--- DataTransfer で作り直す ---')
const dt = new DataTransfer()
dt.items.add(input.files[0])
dt.items.add(input.files[1])
for (let i = 0; i < dt.items.length; i++) {
console.log(dt.items[i].getAsFile().name)
}
// input.files に代入し直すと、選択状態そのものを差し替えられる
// input.files = dt.files
// console.log('--- 差し替え後の FileList ---')
// console.log('件数:', input.files.length)
// console.log('残ったファイル:', input.files[0].name)
})
</script>
DataTransfer を使うと配列操作的なことができるが、dt.files 自体はあくまで FileList なので、length は使えても forEach などの配列メソッドは使えない点は変わらない。
まとめ
| 項目 | FileList | DataTransfer |
|---|---|---|
| 正体 |
input.files が返すファイル一覧オブジェクト |
データを一時的に運ぶ「箱」 |
| 配列か | 配列ではない(Array-like) |
items / files を持つ |
length |
使える |
items.length が使える |
| インデックス参照 |
files[0] で可 |
items[0].getAsFile() で可 |
| ファイルの追加・削除 | ❌ できない | ✅ items.add() で可 |
forEach などの配列メソッド |
❌ 使えない | ❌ dt.files も FileList なので使えない |
ファイルの追加・削除をしたい時は、DataTransfer で新しい FileList を組み立てて input.files に代入し直す、というのが定石。
感想
- ドラックアンドドロップ、コピペをよく考えたら保存する場所必要だよね
- DOM操作まだまだ弱いなぁ