やりたいこと
前回までで1枚の画像をリサイズして返す仕組みができた。
次は複数ファイルをまとめて処理したい。
やり方はいくつかあって、全部並列で投げる方法と、
1枚ずつ順番に処理する方法があります。
並列の方が速そうに見えるけど、画像が多いと一気にメモリを食うので
実用的には順番に処理する方が安定することが多いです。
コード
const worker = new Worker('worker.js'); // 前回と同じ worker.js を使う
async function processFiles(files) {
const results = [];
for (const file of files) {
const result = await processOne(file);
results.push(result);
// 1枚終わるたびに進捗を更新
updateProgress(results.length, files.length);
}
return results;
}
function processOne(file) {
return new Promise(async (resolve) => {
const bitmap = await createImageBitmap(file);
worker.onmessage = (e) => resolve({ ...e.data, name: file.name });
worker.postMessage(
{ bitmap, maxWidth: 800, maxHeight: 800 },
[bitmap]
);
});
}
function updateProgress(done, total) {
progress.textContent = `${done} / ${total} 完了`;
}
HTML 側
<input type="file" id="input" accept="image/*" multiple />
<p id="progress"></p>
<div id="results"></div>
<script>
const input = document.getElementById('input');
const progress = document.getElementById('progress');
const results = document.getElementById('results');
input.addEventListener('change', async (e) => {
const files = Array.from(e.target.files);
progress.textContent = `0 / ${files.length} 完了`;
const processed = await processFiles(files);
processed.forEach(({ blob, name, width, height }) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `resized_${name}`;
a.textContent = `${name}(${width}×${height})`;
results.appendChild(a);
results.appendChild(document.createElement('br'));
});
});
</script>
for...of で回す理由
files.forEach に async を渡しても、中の await が正しく効かないので
直列処理にならないことがあります。
for...of の中で await を使うと、1枚の処理が終わってから
次に進む直列処理になるので、メモリの使われ方が予測しやすいです。
試してみてわかったこと
20〜30枚くらいまでは特に問題なく動きました。
100枚以上になってくると、処理自体は動くけど
ダウンロードリンクが増えすぎて画面が見づらくなったので
ZIPにまとめて落とせるようにした方がよさそうだと感じています。
そのあたりは次以降で触れる予定です。
次は出力フォーマットの話で、WebP と AVIF への変換を書きます。
自分でゼロから実装するのが大変な場合は、
同じ仕組みで動くブラウザツール BulkPicTools も作っているので、
よかったら使ってみてください → https://bulkpictools.com