🍄

画像ファイルをWebPに変換して複数サイズ用意するのを自動化したい

2023/08/21に公開

Web サイトで表示画像の最適化をするため、以下のようなコードで複数の大きさの画像が画面サイズにより切り替わるようにしています。

<picture>
  <source type="image/webp" sizes="(max-width: 899px) 100vw, 584px" srcset="
      /images/sample-640w.webp 640w,
      /images/sample-750w.webp 750w,
      /images/sample-1080w.webp 1080w,
      /images/sample-1200w.webp 1200w,
      /images/sample-1920w.webp 1920w
    ">
  <img src="/images/sample.png" width="584" height="480" alt="">
</picture>

画像のサイズは、Next.js の画像コンポーネントで生成されるサイズバリエーションを参考にしつつ、自サイトのメディアクエリに合わせて少し削りました。

コードは良いのですが、これを実現するには画像1種類につき、 WebP 非対応ブラウザ用の画像1ファイル+サイズ別の WebP 画像5ファイルで、合計6ファイルの画像を生成しなければいけません。
デザインは Figma で行っているのですが、Figma で WebP 出力に対応していなかったので複数サイズの png で書き出し後、画像変換サイトで WebP に変換していたりしました。
が、枚数が多いと面倒なので、自動でこれらを実行できるようスクリプト化しました。

やりたいこと

  • png, jpeg ファイルを WebP 画像に変換して複数サイズにリサイズ
  • png, jpeg ファイルは圧縮
  • フォルダ内の画像に一括実行

スクリプト

画像変換関連のパッケージをインストールします。

% yarn add -D imagemin imagemin-jpegtran imagemin-pngquant imagemin-webp

作成したスクリプトは以下です。

script.mjs
import glob from "glob";
import fs from "graceful-fs";
import imagemin from "imagemin";
import imageminJpegtran from "imagemin-jpegtran";
import imageminPngquant from "imagemin-pngquant";
import imageminWebp from "imagemin-webp";
import { promises as fsPromises } from "node:fs";
import path from "node:path";
import { promisify } from "node:util";

const SRC_DIR = "./images";
const DIST_DIR = "./build/images";
const WEBP_SIZES = [640, 750, 1080, 1200, 1920];

const writeFile = promisify(fs.writeFile);

const srcFiles = glob.sync(SRC_DIR + "/**/*.*");

console.log("Images optimized");
srcFiles.forEach((file) => {
  if (file.match(/.*-1920w\.[png|jpg]/)) {
    // サイズ別作成用の画像(SIZES の最大サイズ 1920w)はサイズバリエーションで WebP 生成
    WEBP_SIZES.forEach((size) => {
      imagemin([file], {
        plugins: [
          imageminWebp({ quality: 80, resize: { width: size, height: 0 } }),
        ],
      }).then((files) => {
        files.forEach(async (v) => {
          const source = path.parse(v.sourcePath);
          const destinationPath = `${source.dir.replace(
            SRC_DIR,
            DIST_DIR
          )}/${source.name.replace("-1920w", `-${size}w`)}.webp`;
          await fsPromises.mkdir(path.dirname(destinationPath), {
            recursive: true,
          });
          await writeFile(destinationPath, v.data);
          console.log(`${v.sourcePath} -> ${destinationPath}`);
        });
      });
    });
  } else if (file.match(/.*\.[png|jpg|jpeg]/)) {
    // それ以外の png, jpg 画像は圧縮
    imagemin([file], {
      plugins: [
        imageminPngquant({
          quality: [0.7, 0.95],
        }),
        imageminJpegtran({ size: 244 }),
      ],
    }).then((files) => {
      files.forEach(async (v) => {
        const source = path.parse(v.sourcePath);
        const destinationPath = `${source.dir.replace(SRC_DIR, DIST_DIR)}/${
          source.name
        }${source.ext}`;
        await fsPromises.mkdir(path.dirname(destinationPath), {
          recursive: true,
        });
        await writeFile(destinationPath, v.data);
        console.log(`${v.sourcePath} -> ${destinationPath}`);
      });
    });
  } else {
    // それ以外のファイル(SVG 等)はそのままコピー
    const source = path.parse(file);
    const destinationPath = `${source.dir.replace(SRC_DIR, DIST_DIR)}/${
      source.name
    }${source.ext}`;
    fs.cp(file, destinationPath, (err) => {
      if (err) throw err;
      console.log(`${file} -> ${destinationPath}`);
    });
  }
});

使い方

Figma からは画像を2枚出力します。

ファイル名 拡張子 サイズ 用途
{任意の名前}-1920w png, jpg w:1920 WebP 生成用
{任意の名前} png, jpg, jpeg 任意 WebP 非対応時のフォールバック用、サイズは画面表示サイズに合わせる

これを SRC_DIR (images) ディレクトリに保存します。

root
|-- images
|     |-- sample-1920w.png
|     |-- sample.png
|-- build/images
|-- script.mjs

先程のスクリプトを実行します。

% node ./imgcompress.mjs

DIST_DIR (./build/images) ディレクトリ配下に、生成された画像が保存されます。

root
|-- images
|     |-- sample-1920w.png
|     |-- sample.png
|-- build/images
|     |-- sample-640w.webp
|     |-- sample-750w.webp
|     |-- sample-1080w.webp
|     |-- sample-1200w.webp
|     |-- sample-1920w.webp
|     |-- sample.png
|-- script.mjs

生成したい画像サイズを変更したい場合は、スクリプト中の WEBP_SIZES を変更して、画像はその最大サイズに合わせて用意してください。

拡張子が png, jpg, jpeg 以外のファイル (SVG 等) は何もせずコピーして build/images ディレクトリに保存しますので、画像ファイルはまとめて images ディレクトリに放り込んでおいて大丈夫です。

まとめ

サイズバリエーションをもっと細かくしたくなっても対応しやすく、手間が減りました🍄

Discussion