JavaScriptで画像リサイズ


概要

- `input[type="file"]` で選んだ画像を 長辺が最大 `1600px` になるようにリサイズして、画面に表示する最小構成のサンプル - `createImageBitmap` が使える環境では高速に、未対応でも `` ロードでフォールバック) - ポイント - 長辺が `1600px` を超える場合だけ縮小(アスペクト比維持) - `canvas.toBlob()` で生成した `Blob` を `` に表示(`dataURL` より低メモリ) - 元が `PNG` なら `PNG`、それ以外は `JPEG` で出力(品質は `0.9`) - 補足 - `EXIF` の回転情報(`Orientation`) はこの簡易版では反映していない - スマホ撮影 `JPEG` で縦横が合わない場合は、`EXIF` を読んで回転させる処理(例:`exifreader` 等のライブラリで角度を取得し、`ctx.rotate()` で補正)を追加 - 表示だけなら `` をそのまま `DOM` に挿入しても良いがが、上記は再利用しやすいように `` に出力 - 画質と容量のバランスを調整したい場合は、`quality`(`JPEG` のみ)や `MAX_LONG_EDGE` を変更

デモ

画像を長辺1600pxにリサイズして表示

ここに画像をドラッグ&ドロップしてもOK

リサイズ後のプレビュー

コード

JavaScript

const MAX_LONG_EDGE = 1600;
const fileInput = document.getElementById('fileInput');
const preview  = document.getElementById('preview');
const infoEl   = document.getElementById('info');
const dropzone = document.getElementById('dropzone');

let currentObjectURL = null; // 表示中の Blob URL を後で解放

fileInput.addEventListener('change', async (e) => {
  const file = e.target.files?.[0];
  if (file) await handleFile(file);
  // 同じファイル選択でも change が発火するように value をクリア
  e.target.value = '';
});

// ドラッグ&ドロップ対応
['dragenter','dragover'].forEach(type => {
  dropzone.addEventListener(type, (ev) => {
    ev.preventDefault(); ev.stopPropagation();
    dropzone.classList.add('dragover');
  });
});
['dragleave','drop'].forEach(type => {
  dropzone.addEventListener(type, (ev) => {
    ev.preventDefault(); ev.stopPropagation();
    dropzone.classList.remove('dragover');
  });
});
dropzone.addEventListener('drop', async (ev) => {
  const file = ev.dataTransfer?.files?.[0];
  if (file) await handleFile(file);
});

async function handleFile(file) {
  if (!file.type.startsWith('image/')) {
    alert('画像ファイルを選択してください。');
    return;
  }
  try {
    // 1) 画像の読み込み(createImageBitmap があれば高速)
    const { width, height, drawToCanvas } = await loadImage(file);

    // 2) 縮小スケール計算(長辺が MAX_LONG_EDGE を超える場合のみ縮小)
    const longEdge = Math.max(width, height);
    const scale    = longEdge > MAX_LONG_EDGE ? (MAX_LONG_EDGE / longEdge) : 1;
    const outW     = Math.round(width * scale);
    const outH     = Math.round(height * scale);

    // 3) 描画用キャンバス
    const canvas = document.createElement('canvas');
    canvas.width = outW;
    canvas.height = outH;
    const ctx = canvas.getContext('2d', { alpha: true });

    // 高品質縮小(ブラウザ依存)
    ctx.imageSmoothingEnabled = true;
    ctx.imageSmoothingQuality = 'high';

    // 4) 実描画
    await drawToCanvas(ctx, outW, outH);

    // 5) Blob 化して  に表示(PNG か JPEG を選択)
    const outType = file.type.includes('png') ? 'image/png' : 'image/jpeg';
    const quality = outType === 'image/jpeg' ? 0.9 : 1.0;

    const blob = await new Promise(resolve => canvas.toBlob(resolve, outType, quality));
    if (!blob) throw new Error('toBlob に失敗しました');

    // 既存の URL を解放
    if (currentObjectURL) URL.revokeObjectURL(currentObjectURL);

    const url = URL.createObjectURL(blob);
    currentObjectURL = url;
    preview.src = url;

    // 情報表示
    const kb = (blob.size / 1024).toFixed(1);
    infoEl.textContent =
      `元サイズ: ${width}×${height} → 出力: ${outW}×${outH}, ` +
      `形式: ${outType}, 約 ${kb} KB`;
  } catch (err) {
    console.error(err);
    alert('画像の処理に失敗しました。別の画像でお試しください。');
  }
}

/**
  * 画像を読み込み、任意のキャンバスに描画する関数を返す。
  * createImageBitmap 利用時は ImageBitmap を、フォールバックは HTMLImageElement を利用。
  */
async function loadImage(file) {
  if ('createImageBitmap' in window) {
    const bitmap = await createImageBitmap(file); // 高速デコード
    const width  = bitmap.width;
    const height = bitmap.height;
    return {
      width, height,
      drawToCanvas: async (ctx, w, h) => {
        ctx.drawImage(bitmap, 0, 0, w, h);
        bitmap.close?.();
      }
    };
  } else {
    // フォールバック:  + Object URL
    const url = URL.createObjectURL(file);
    try {
      const img = await loadImageElement(url);
      const width  = img.naturalWidth || img.width;
      const height = img.naturalHeight || img.height;
      return {
        width, height,
        drawToCanvas: async (ctx, w, h) => {
          ctx.drawImage(img, 0, 0, w, h);
        }
      };
    } finally {
      // 読み込み用 URL は不要になったら解放
      URL.revokeObjectURL(url);
    }
  }
}

function loadImageElement(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    // CORS 回避が必要な外部 URL を使う場合は crossOrigin 設定が必要
    // img.crossOrigin = 'anonymous';
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = url;
  });
}