Oh MyUtils

图片压缩工具 - 压缩PNG、JPG和WebP 在线

在保持质量的同时压缩图片。带前后对比减小PNG、JPG和WebP文件大小 — 无需上传,100%浏览器处理。

常见问题

什么是图片压缩,为什么需要它?

图片压缩通过删除不必要的数据或应用更高效编码图片的数学算法来减小图片文件大小。对于Web开发,压缩后的图片可以带来更快的页面加载速度、更低的带宽消耗、更好的Core Web Vitals分数和更高的SEO排名。一张典型的5MB DSLR照片可以在视觉质量损失最小的情况下压缩到500KB以下。

如何使用这个图片压缩工具?

1. 将图片拖放到上传区域(或点击浏览文件)。2. 调整质量滑块(默认80%,是一个好的平衡点)。3. 可选设置最大宽度/高度来调整大小。4. 点击单个图片的"压缩"或"全部压缩"进行批量处理。5. 在对比标签页预览压缩前后效果。6. 下载单个图片或使用"全部下载(ZIP)"获取ZIP压缩包。

我的图片数据安全吗?图片会上传到服务器吗?

您的图片100%安全,永远不会离开您的浏览器。所有压缩都使用HTML5 Canvas API和OffscreenCanvas在客户端执行。没有任何图片数据会传输到任何服务器,使得这个工具完全私密。页面加载后您甚至可以离线使用。这是我们与TinyPNG等基于服务器的工具的区别。

有损压缩和无损压缩有什么区别?

有损压缩(JPEG、WebP)永久性地删除部分图片数据以获得更小的文件大小。在70-90%的质量设置下,视觉差异通常不可察觉。无损压缩(PNG)精确保留所有图片数据,文件更大但质量完美。JPEG和WebP默认使用有损压缩;使用Canvas API时PNG始终是无损的。

我应该使用哪种图片格式:JPEG、PNG还是WebP?

JPEG最适合照片和具有丰富色彩和渐变的复杂图片(不支持透明度)。PNG最适合图形、图标、截图和有文字或锐利边缘的图片(支持透明度,文件较大)。WebP是一种现代格式,在同等质量下通常比JPEG小25-35%,支持有损和无损压缩及透明度,全球浏览器支持率达95%以上,推荐用于Web。

为什么我的PNG文件压缩后反而变大了?

Canvas API生成无损PNG输出,这可能与优化的PNG编码器(如OptiPNG或pngquant)不同。如果原始PNG已经很好地优化过,通过Canvas重新编码可能会产生稍大的文件。要减小PNG大小,请尝试将图片调整为更小的尺寸。

支持的最大文件大小和分辨率是多少?

该工具支持每个文件最大50MB,分辨率最高16384 x 16384像素(大多数浏览器中Canvas API的限制)。对于超高分辨率图片,建议先使用调整大小选项缩小尺寸,这也会加速压缩。在移动设备上,由于内存限制,最大分辨率可能更低。

我可以一次压缩多张图片吗?

是的,批量处理支持同时处理多达20张图片。您可以为所有图片设置全局质量级别。压缩完成后,使用"全部下载(ZIP)"将所有压缩后的图片下载为一个ZIP文件。

代码示例

// Client-side image compression using Canvas API
async function compressImage(file, quality = 0.8) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    const url = URL.createObjectURL(file);

    img.onload = () => {
      URL.revokeObjectURL(url);

      const canvas = document.createElement('canvas');
      canvas.width = img.width;
      canvas.height = img.height;

      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0);

      // Determine output type (use WebP if supported, fallback to JPEG)
      const outputType = file.type === 'image/png' ? 'image/png' : 'image/webp';

      canvas.toBlob(
        (blob) => {
          if (!blob) {
            reject(new Error('Compression failed'));
            return;
          }
          resolve({
            blob,
            originalSize: file.size,
            compressedSize: blob.size,
            reductionPercent: Math.round((1 - blob.size / file.size) * 100),
          });
        },
        outputType,
        outputType === 'image/png' ? undefined : quality
      );
    };

    img.onerror = () => {
      URL.revokeObjectURL(url);
      reject(new Error('Failed to load image'));
    };

    img.src = url;
  });
}

// Compress with resize
async function compressAndResize(file, maxWidth, maxHeight, quality = 0.8) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    const url = URL.createObjectURL(file);

    img.onload = () => {
      URL.revokeObjectURL(url);

      let { width, height } = img;

      // Calculate new dimensions maintaining aspect ratio
      if (maxWidth && width > maxWidth) {
        height = Math.round((height * maxWidth) / width);
        width = maxWidth;
      }
      if (maxHeight && height > maxHeight) {
        width = Math.round((width * maxHeight) / height);
        height = maxHeight;
      }

      const canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;

      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, width, height);

      canvas.toBlob(
        (blob) => {
          if (!blob) {
            reject(new Error('Compression failed'));
            return;
          }
          resolve({
            blob,
            originalSize: file.size,
            compressedSize: blob.size,
            width,
            height,
            reductionPercent: Math.round((1 - blob.size / file.size) * 100),
          });
        },
        'image/jpeg',
        quality
      );
    };

    img.onerror = () => {
      URL.revokeObjectURL(url);
      reject(new Error('Failed to load image'));
    };

    img.src = url;
  });
}

// Batch compression
async function compressBatch(files, quality = 0.8) {
  const results = [];
  for (const file of files) {
    try {
      const result = await compressImage(file, quality);
      results.push({ filename: file.name, ...result });
    } catch (error) {
      results.push({ filename: file.name, error: error.message });
    }
  }
  return results;
}

// Usage
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
  const files = Array.from(e.target.files);
  const results = await compressBatch(files, 0.8);

  results.forEach((result) => {
    if (result.error) {
      console.error(`${result.filename}: ${result.error}`);
    } else {
      console.log(
        `${result.filename}: ${result.originalSize} -> ${result.compressedSize} (${result.reductionPercent}% reduction)`
      );
    }
  });
});

相关工具