きっかけ
画像リサイズのためだけに Sharp を入れるのが、なんとなく嫌だった。
Node.js 環境ならともかく、ブラウザで動かすだけなのにバンドルサイズを
増やしたくないという気持ちがあって、Canvas API だけで書いてみたら
意外とあっさり動いたので残しておきます。
コード全体
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>画像リサイズ</title>
</head>
<body>
<input type="file" id="input" accept="image/*" />
<br><br>
<canvas id="canvas"></canvas>
<br>
<a id="download" download="resized.png">ダウンロード</a>
<script>
const input = document.getElementById('input');
const canvas = document.getElementById('canvas');
const download = document.getElementById('download');
const ctx = canvas.getContext('2d');
const MAX_WIDTH = 800;
const MAX_HEIGHT = 800;
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const img = new Image();
img.onload = () => {
const { width, height } = calcSize(img.width, img.height);
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
download.href = canvas.toDataURL('image/png');
download.textContent = `ダウンロード(${width}×${height})`;
};
img.src = URL.createObjectURL(file);
});
function calcSize(w, h) {
if (w <= MAX_WIDTH && h <= MAX_HEIGHT) return { width: w, height: h };
const ratio = Math.min(MAX_WIDTH / w, MAX_HEIGHT / h);
return {
width: Math.round(w * ratio),
height: Math.round(h * ratio),
};
}
</script>
</body>
</html>
ファイルを選ぶと Canvas に描画されて、ダウンロードリンクが出てくるだけです。
ポイントを3つだけ
URL.createObjectURL を使う
img.src = URL.createObjectURL(file);
FileReader で Base64 に変換するコードをよく見かけますが、大きなファイルだと
変換だけで時間がかかります。createObjectURL はメモリ上のオブジェクトを
直接参照するので速いし、コードもシンプルです。
縦横比の維持は Math.min で
const ratio = Math.min(MAX_WIDTH / w, MAX_HEIGHT / h);
縦と横それぞれのスケール比を出して、小さい方に合わせる。
これだけで縦長・横長どちらの画像でも意図した動きになります。
出力形式は toDataURL の引数で変えられる
canvas.toDataURL('image/jpeg', 0.85); // JPEG、品質85%
canvas.toDataURL('image/png'); // PNG(デフォルト)
WebP は Chrome・Edge では動きますが Safari(特に古いバージョン)では
出力できないことがあるので、対応ブラウザを確認してから使う方が無難です。
使ってみて気になったこと
4000×3000px くらいの写真を何枚も連続で処理しようとすると、
ブラウザが重くなることがありました。1枚ずつ処理する分には特に問題ないですが、
まとめて処理したい場合は Web Worker に逃がした方が良さそうです。
そのあたりはまた試したら書きます。