Skip to content

图片批量压缩工具(基于 Sharp)

使用场景

  • UI 给的图片体积过大(几 MB)
  • 一张张压缩效率极低
  • 希望一键压缩 + 自动输出

核心思路

  • 使用 sharp 进行图片压缩
  • 支持 jpg / png / webp
  • 可配置最大尺寸、压缩质量
  • 自动生成压缩报告

技术选型

  • sharp:高性能图片处理库
  • fs-extra:增强版文件系统 API
  • Node.js 脚本自动化处理

compress-images-advanced.js

js

// compress-images-advanced.js
const sharp = require('sharp');
const fs = require('fs-extra');
const path = require('path');
// 配置选项
const config = {
  inputDir: 'src/images',
  outputDir: 'dist/images',
  // 保持原格式
  keepOriginalFormat: true,
  // 可选:设置最大尺寸
  resize: {
    enabled: true,
    maxWidth: 1920,
    maxHeight: 1080
  },
  // 格式特定的质量设置
  quality: {
    jpg: 80,
    png: 80,
    webp: 80,
    tiff: 80
  }
};
async function compressImages() {
  const { inputDir, outputDir, keepOriginalFormat, resize, quality } = config;
  
  await fs.ensureDir(outputDir);
  
  const files = await fs.readdir(inputDir);
  let results = [];
  
  console.log('🚀 开始批量图片处理...\n');
  
  for (const file of files) {
    const result = await processFile(file, {
      inputDir,
      outputDir,
      keepOriginalFormat,
      resize,
      quality
    });
    results.push(result);
  }
  
  generateReport(results);
}
async function processFile(file, options) {
  const { inputDir, outputDir, keepOriginalFormat, resize, quality } = options;
  const ext = path.extname(file).toLowerCase();
  const inputPath = path.join(inputDir, file);
  const outputPath = path.join(outputDir, file);
  
  try {
    const stats = await fs.stat(inputPath);
    const originalSize = stats.size;
    
    let sharpInstance = sharp(inputPath);
    
    // 如果需要调整尺寸
    if (resize.enabled) {
      sharpInstance = sharpInstance.resize({
        width: resize.maxWidth,
        height: resize.maxHeight,
        fit: 'inside',
        withoutEnlargement: true
      });
    }
    
    // 根据格式处理
    switch (ext) {
      case '.jpg':
      case '.jpeg':
        await sharpInstance
          .jpeg({ quality: quality.jpg, mozjpeg: true })
          .toFile(outputPath);
        break;
      case '.png':
        await sharpInstance
          .png({ quality: quality.png, compressionLevel: 9 })
          .toFile(outputPath);
        break;
      case '.webp':
        await sharpInstance
          .webp({ quality: quality.webp, effort: 6 })
          .toFile(outputPath);
        break;
      default:
        // 不支持的格式或保持原格式
        await fs.copy(inputPath, outputPath);
    }
    
    const compressedStats = await fs.stat(outputPath);
    const compressedSize = compressedStats.size;
    
    return {
      file,
      originalSize,
      compressedSize,
      success: true,
      format: ext
    };
    
  } catch (error) {
    console.error(`❌ 处理 ${file} 失败:`, error.message);
    // 出错时复制原文件
    await fs.copy(inputPath, outputPath);
    const stats = await fs.stat(inputPath);
    
    return {
      file,
      originalSize: stats.size,
      compressedSize: stats.size,
      success: false,
      error: error.message,
      format: ext
    };
  }
}
function generateReport(results) {
  const successful = results.filter(r => r.success);
  const failed = results.filter(r => !r.success);
  
  let totalOriginal = 0;
  let totalCompressed = 0;
  
  successful.forEach(r => {
    totalOriginal += r.originalSize;
    totalCompressed += r.compressedSize;
  });
  
  console.log('\n📊 ========== 处理报告 ==========');
  console.log(`总计文件: ${results.length}`);
  console.log(`成功处理: ${successful.length}`);
  console.log(`处理失败: ${failed.length}`);
  
  if (failed.length > 0) {
    console.log('\n❌ 失败文件:');
    failed.forEach(f => console.log(`  - ${f.file}: ${f.error}`));
  }
  
  console.log('\n💰 空间节省统计:');
  console.log(`原始总大小: ${formatSize(totalOriginal)}`);
  console.log(`压缩后总大小: ${formatSize(totalCompressed)}`);
  
  if (totalOriginal > 0) {
    const saved = totalOriginal - totalCompressed;
    const percent = (saved / totalOriginal * 100).toFixed(1);
    
    if (saved > 0) {
      console.log(`节省空间: ${formatSize(saved)} (${percent}%)`);
    } else {
      console.log('⚠️  文件大小未减少(可能已是最佳质量)');
    }
  }
  
  console.log('================================\n');
  console.log('✅ 所有文件已保存到 dist/images 目录');
}
function formatSize(bytes) {
  if (bytes === 0) return '0 B';
  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 运行
compressImages();

功能亮点

  • ✅ 保持原图片格式
  • ✅ 自动限制最大分辨率(防止放大)
  • ✅ 不同格式独立质量配置
  • ✅ 自动统计压缩前后体积
  • ✅ 异常兜底(失败则复制原图)

使用步骤

  1. 安装依赖
bash
npm install sharp fs-extra
  1. 放入待压缩图片
text
src/images/
  1. 执行命令
bash
node compress-images-advanced.js
  1. 输出结果
text
dist/images/

终端会自动输出:

  • 成功 / 失败数量
  • 压缩前后体积
  • 节省空间百分比