JavaScriptでファイル名順に複数ファイルを読み込む方法
INPUTタグで複数ファイルを読み込む際、ブラウザ内で処理するときはFileReaderオブジェクトを使うことが多いと思います。
しかしこれは非同期処理なので、はてファイル名順に処理する場合はどうしたものか、とちょっと悩んだので備忘録としてメモしておきます。
具体的には連番の画像ファイルを順番に並べたい、とかですね。
バラバラに処理しても良い場合
サンプルコード
<input type="file" id="input_file" multiple>
<script>
let imgArray = [];
document.getElementById('input_file').onchange = function () {
for (let i = 0; i < this.files.length; i++) {
let reader = new FileReader();
reader.onload = function() {
let img = new Image();
img.src = this.result;
img.onload = function () {
imgArray.push(img); //←ここで画像データをメモリ内に保存
}
};
reader.readAsDataURL(this.files[i]);
}
};
</script>
解説
選択された複数の画像ファイルをメモリ中にロードする例ですが、このコードではどの順番に画像が読み込まれるか不確定です。実際試してみると01.png,02.png,03.png...という連番ファイルであっても、02.png,01.png,03.pngというような順番に読み込まれることがあります。
ファイル名をキーにして連想配列をソートする
サンプルコード
<input type="file" id="input_file" multiple>
<script>
let imgArray = [];
document.getElementById('input_file').onchange = function () {
let imgTemp= []; //←一時保存用
let files_length = this.files.length;
for (let i = 0; i < this.files.length; i++) {
let reader = new FileReader();
let filename = this.files[i].name;
reader.onload = function() {
let img = new Image();
img.src = this.result;
img.onload = function () {
imgTemp[filename] = this;
if (Object.keys(imgTemp).length >= files_length) {
imgArray = [];
let keys = Object.keys(imgTemp);
keys.sort();
for (key of keys) {
imgArray.push(imgTemp[key]);
}
}
}
};
reader.readAsDataURL(this.files[i]);
}
};
</script>
解説
…若干、メモリの無駄遣いというか、強引な気がしなくもないですが、一時的にファイル名をキーとした連想配列(imgTemp)を作り、選択されたファイル数まで揃ったら、キーをソートして、保存用の配列(imgArray)にキー順にほおりこむ、という処理をしています。
まとめ
もっとスマートなやり方がありそうなのでググってみたのですが、むしろFileReaderのonload中にファイル名を取得するにはどうすれば良いんだ?という掲示板の書き込みとか、それをなんかやたら小難しい方法で解決している書き込みしか見当たりませんでした。
FileReaderオブジェクトにファイル名を保存しておければわかりやすいんですけどね。
見た目的にreader.onloadの中からfilenameを参照したら、後で書き換わるように感じるのかも知れません。昔のJavaScriptのvar変数はグローバルスコープだったから、というのも理由のひとつかも。
いずれにせよ、ループ内でlet宣言で作成された変数filenameは、onloadでセットしている無名関数に値渡しされるので、どんな順番で呼ばれようとfilenameが書き換わることはありません。
…などと言いつつ、自分自身が3歩歩いたら忘れそうなのでここにメモしておきました。