sharpで画像を一括圧縮、WebP・AVIF変換する
- Category:
- 開発環境
- Tags:
- JavaScript, 画像, 画像圧縮, 開発
画像変換ライブラリsharpを用いて、画像をまとめて圧縮や変換(Webp・AVIF・JPG・PNG)できる方法をまとめました。
ベースコード
本記事で紹介するサンプルコードは、以下の記事で紹介されているメインのコードをベースとして利用させていただいています。
※利用目的がオリジナル記事で完結できそうな場合は以下をご参照ください。
この記事のsharpでできること
- 画像(JPG、PNG、GIF)の圧縮
- WebP変換
- AVIF変換
- JPG変換
- PNG変換
- 圧縮と変換処理を同時に実行
- 例)ベース画像(jpg)を圧縮しつつWebPやAVIFに変換するなど
ディレクトリ構成
変換前の画像ファイル一式を images/ に格納する構成です。
■プロジェクトディレクトリ
┣ dist (処理後の画像が出力されるディレクトリ) ※コマンド実行時に自動追加
┣ images (処理する前の画像を格納) ※手動作成してください
┃
┣ config.mjs (各処理の設定を記載するファイル) ※手動作成してください(詳細後述)
┣ convert-avif.mjs (AVIF変換処理ファイル) ※手動作成してください(詳細後述)
┣ convert-jpg.mjs (JPG変換処理ファイル) ※手動作成してください(詳細後述)
┣ convert-png.mjs (PNG変換処理ファイル) ※手動作成してください(詳細後述)
┣ convert-webp.mjs (WebP変換処理ファイル) ※手動作成してください(詳細後述)
┣ optimise.mjs (画像圧縮処理ファイル) ※手動作成してください(詳細後述)
┃
┣ package.json (プロジェクトのjsonファイル) ※初期設定(sharpインストール)時に自動追加
┃
┗ node_modules (編集不要:自動生成されるコアファイル格納場所) ※初期設定(sharpインストール)時に自動追加
初期設定(ファイルのインストール)
構築したいディレクトリへ移動
作業を始める前にsharpを構築したいディレクトリまで移動してください。
Windowsでの移動
cd C:¥Users¥.....¥sharpを構築したいプロジェクトディレクトリ
Macでの移動
cd /Users/....../sharpを構築したいプロジェクトディレクトリ
macなら該当フォルダを右クリック→「フォルダに新規ターミナルタブ」を選択してターミナルを立ち上げるとスムーズです。
sharpをインストール
以下のコマンドを実行してsharpをインストールします。
npm install -D sharp
sharpの設定用JSファイルを作成
開発ディレクトリに各処理の設定を記載するJSファイル(config.mjs)と、5つの処理をそれぞれ記載するJSファイル(optimise.mjs、convert-webp.mjs、convert-avif.mjs、convert-jpg.mjs、convert-png.mjs)計6つのファイルを作成します。
config.mjs (各処理の設定を記載するファイル)
config.mjs
/*
sharpライブラリを使用して画像を圧縮する
https://sharp.pixelplumbing.com/
参考
https://zenn.dev/spicato_blog/articles/6afdf43d0f0a97
*/
import fs from 'fs';
import path from 'path';
import sharp from 'sharp';
//対象ファイルの格納先
const dirName = './images';
//変換後ファイルの格納先
const outPutDir = `./dist`;
//jpgオプション 詳細: https://sharp.pixelplumbing.com/api-output#jpeg
const jpgOptions = {
quality: 70,
mozjpeg: true
}
//pngオプション 詳細: https://sharp.pixelplumbing.com/api-output#png
const pngOptions = {
effort: 10,
quality: 70,
compressionLevel: 9
}
//gifオプション 詳細: https://sharp.pixelplumbing.com/api-output#gif
const gifOptions = {
quality: 70
}
//webpオプション 詳細: https://sharp.pixelplumbing.com/api-output#webp
const webpOptions = {
quality: 70,
alpha_quality: 0
}
//avifオプション 詳細: https://sharp.pixelplumbing.com/api-output#avif
const avifOptions = {
quality: 50
}
async function imageConversion (options) {
options = Object.assign({
isOptimize: true,
isWebp: true,
isAvif: true,
isJpg: true,
isPng: true
}, options);
const { isOptimize, isWebp, isAvif, isJpg, isPng } = options;
// 拡張子を確認
function getExtension(file) {
const ext = path.extname(file || '').split('.');
return ext[ext.length - 1];
}
const readSubDir = (folderPath, finishFunc) => {
// フォルダ内の全ての画像の配列
let result = [];
let execCounter = 0;
const readTopDir = (folderPath) => {
execCounter += 1;
fs.readdir(folderPath, (err, items) => {
//.から始まる隠しファイルを除外
items = items.filter((item) => {
return item.indexOf('.') !== 0;
});
if (err) {
console.log(err);
}
items = items.map((itemName) => {
return path.join(folderPath, itemName);
});
items.forEach((itemPath) => {
if (fs.statSync(itemPath).isFile()) {
result.push(itemPath);
}
if (fs.statSync(itemPath).isDirectory()) {
//フォルダなら再帰呼び出し
readTopDir(itemPath);
}
});
execCounter -= 1;
if (execCounter === 0) {
if (finishFunc) {
finishFunc(result);
}
}
});
};
readTopDir(folderPath);
};
//サブディレクトリの列挙 非同期
readSubDir(dirName, (items) => {
items.forEach((item) => {
const pathName = path.dirname(item);
const fileName = path.basename(item);
const fileFormat = getExtension(fileName);
// もしディレクトリがなければ作成
if (!fs.existsSync(outPutDir)) {
fs.mkdirSync(outPutDir);
}
//非対応ファイルの簡易チェック
if (fileFormat === '') {
//拡張子なし
console.log(
`\u001b[1;31m 対応していないファイルです。-> ${fileName}`
);
return;
} else if (fileFormat === 'svg') {
// svgは複製のみ
fs.copyFile(item, `${outPutDir}/${fileName}`, (err) => {
if (err) {
return;
}
console.log(
`\u001b[1;32m ${fileName}を${outPutDir}に複製しました。`
);
});
return;
}
//JPG、PNG、GIFファイルを圧縮
if(isOptimize) {
let sh = sharp(`${pathName}/${path.basename(item)}`);
if (fileFormat === 'JPG' || fileFormat === 'JPEG' || fileFormat === 'jpg' || fileFormat === 'jpeg') {
sh = sh.jpeg(jpgOptions);
} else if (fileFormat === 'PNG' || fileFormat === 'png') {
sh = sh.png(pngOptions);
} else if (fileFormat === 'GIF' || fileFormat === 'gif') {
sh = sh.gif(gifOptions);
} else {
console.log(
`\u001b[1;31m 対応していないファイルです。-> ${fileName}`
);
return;
}
sh.toFile(`${outPutDir}/${fileName}`, (err, info) => {
if (err) {
console.error(err);
return;
}
console.log(
`\u001b[1;32m ${fileName}を圧縮しました。 ${info.size / 1000}KB`
);
});
}
//webp変換
if (isWebp) {
sharp(`${pathName}/${path.basename(item)}`).webp(webpOptions).toFile(
`${outPutDir}/${fileName.replace(
/\.[^/.]+$/,
'.webp'
)}`,
(err, info) => {
if (err) {
console.error(err);
return;
}
console.log(
`\u001b[1;32m ${fileName}をwebpに変換しました。 ${
info.size / 1000
}KB`
);
}
);
}
//avif変換
if (isAvif) {
sharp(`${pathName}/${path.basename(item)}`).avif(avifOptions).toFile(
`${outPutDir}/${fileName.replace(
/\.[^/.]+$/,
'.avif'
)}`,
(err, info) => {
if (err) {
console.error(err);
return;
}
console.log(
`\u001b[1;32m ${fileName}をavifに変換しました。 ${
info.size / 1000
}KB`
);
}
);
}
//jpg変換
if (isJpg) {
sharp(`${pathName}/${path.basename(item)}`).jpeg(jpgOptions).toFile(
`${outPutDir}/${fileName.replace(
/\.[^/.]+$/,
'.jpg'
)}`,
(err, info) => {
if (err) {
console.error(err);
return;
}
console.log(
`\u001b[1;32m ${fileName}をjpgに変換しました。 ${
info.size / 1000
}KB`
);
}
);
}
//png変換
if (isPng) {
sharp(`${pathName}/${path.basename(item)}`).png(pngOptions).toFile(
`${outPutDir}/${fileName.replace(
/\.[^/.]+$/,
'.png'
)}`,
(err, info) => {
if (err) {
console.error(err);
return;
}
console.log(
`\u001b[1;32m ${fileName}をpngに変換しました。 ${
info.size / 1000
}KB`
);
}
);
}
});
});
}
export { imageConversion };
optimise.mjs (画像圧縮処理ファイル)
optimise.mjs
import { imageConversion } from "./config.mjs";
imageConversion({
isOptimize: true,
isWebp: false,
isAvif: false,
isJpg: false,
isPng: false
});
convert-webp.mjs (WebP変換処理ファイル)
convert-webp.mjs
import { imageConversion } from "./config.mjs";
imageConversion({
isOptimize: false,
isWebp: true,
isAvif: false,
isJpg: false,
isPng: false
});
convert-avif.mjs (AVIF変換処理ファイル)
convert-avif.mjs
import { imageConversion } from "./config.mjs";
imageConversion({
isOptimize: false,
isWebp: false,
isAvif: true,
isJpg: false,
isPng: false
});
convert-jpg.mjs (JPG変換処理ファイル)
convert-jpg.mjs
import { imageConversion } from "./config.mjs";
imageConversion({
isOptimize: false,
isWebp: false,
isAvif: false,
isJpg: true,
isPng: false
});
convert-png.mjs (JPG変換処理ファイル)
convert-png.mjs
import { imageConversion } from "./config.mjs";
imageConversion({
isOptimize: false,
isWebp: false,
isAvif: false,
isJpg: true,
isPng: false
});
package.jsonへコマンドを追加
サンプルの各コマンドは入力時の簡素化目的で短縮記法にしています。
全てのパターンは網羅していないため、一括実行したい組み合わせがない場合は必要に応じて追記してください。
※各コマンドは images/ に変換用画像を格納してから実行してください。
package.json
{
〜省略〜
"scripts": {
"o": "node optimize.mjs",
"w": "node convert-webp.mjs",
"a": "node convert-avif.mjs",
"j": "node convert-jpg.mjs",
"p": "node convert-png.mjs",
"ow": "node optimize.mjs && node convert-webp.mjs",
"oa": "node optimize.mjs && node convert-avif.mjs",
"aw": "node convert-avif.mjs && node convert-webp.mjs",
"all": "node optimize.mjs && node convert-avif.mjs && node convert-webp.mjs"
},
〜省略〜
}
圧縮コマンド
npm run o
もしくは node optimise.mjs
で画像圧縮を実施します。
npm run o
node optimise.mjs
WebP変換コマンド
npm run w
もしくは node convert-webp.mjs
でWebP変換を実施します。
npm run w
node convert-webp.mjs
AVIF変換コマンド
npm run a
もしくは node convert-avif.mjs
でAVIF変換を実施します。
npm run a
node convert-avif.mjs
JPG変換コマンド
npm run j
もしくは node convert-jpg.mjs
でJPG変換を実施します。
npm run j
node convert-jpg.mjs
PNG変換コマンド
npm run p
もしくは node convert-png.mjs
でPNG変換を実施します。
npm run convert
node convert-png.mjs
一括実行コマンド
AVIF変換・WebP変換コマンド
npm run aw
もしくは node convert-avif.mjs && node convert-webp.mjs
でWebP変換とAVIF変換を実施します。
npm run aw
node convert-avif.mjs && node convert-webp.mjs
圧縮・WebP変換コマンド
npm run ow
もしくは node optimize.mjs && node convert-webp.mjs
で元の画像を圧縮しつつWebP変換も実施します。
npm run ow
node optimize.mjs && node convert-webp.mjs
圧縮・AVIF変換コマンド
npm run oa
もしくは node optimize.mjs && node convert-avif.mjs
で元の画像を圧縮しつつAVIF変換も実施します。
npm run oa
node optimize.mjs && node convert-avif.mjs
圧縮・AVIF変換・WebP変換コマンド
npm run all
もしくは node optimize.mjs && node convert-avif.mjs && node convert-webp.mjs
で元の画像を圧縮しつつWebP変換とAVIF変換も実施します。
npm run all
node optimize.mjs && node convert-avif.mjs && node convert-webp.mjs
圧縮・変換のオプション変更
公式ドキュメントの Output options の項目に画像の拡張子ごとに渡せるオプション値がまとめられています。より細かく設定を変更したい場合は参照してください。
各処理の設定場所は、config.mjsの上部に変数でまとめて記載しているので、用途に合わせて適宜調整してください。
WebP変換で透過部分の品質が気になった場合は alpha_quality(0〜100)の値を変更してみてください。
AVIF変換で画質が気になる場合はquality(1〜100)の値を調整してください。
各オプション値については以下記事の「AVIFのオプション」の項目が参考になるかもしれません。
config.mjs (ファイル上部の各画像オプション部分のみ抜粋)
〜省略〜
//jpgオプション 詳細: https://sharp.pixelplumbing.com/api-output#jpeg
const jpgOptions = {
quality: 70,
mozjpeg: true
}
//pngオプション 詳細: https://sharp.pixelplumbing.com/api-output#png
const pngOptions = {
effort: 10,
quality: 70,
compressionLevel: 9
}
//gifオプション 詳細: https://sharp.pixelplumbing.com/api-output#gif
const gifOptions = {
quality: 70
}
//webpオプション 詳細: https://sharp.pixelplumbing.com/api-output#webp
const webpOptions = {
quality: 70,
alpha_quality: 0
}
//avifオプション 詳細: https://sharp.pixelplumbing.com/api-output#avif
const avifOptions = {
quality: 50
}
〜省略〜