使用 Web Worker 与 Canvas 实现高效图片压缩

78 阅读4分钟

使用 Web Worker 与 Canvas 实现高效图片压缩

在现代 Web 应用中,图片处理是常见的需求,尤其是在用户上传图片时,对图片进行压缩可以显著减少上传时间和服务器带宽消耗。然而,图片压缩是一个计算密集型任务,如果在主线程执行,很容易导致页面卡顿甚至无响应。本文将介绍如何结合 Web WorkerCanvas API,在浏览器中实现一个高效且不阻塞 UI 的图片压缩方案。

为什么需要 Web Worker?

传统的图片压缩逻辑通常在主线程中执行,例如读取文件、绘制到 Canvas、导出压缩后的图片等。当处理大尺寸图片时,这些操作会占用大量 CPU 资源,导致主线程繁忙,用户界面冻结,影响用户体验。

Web Worker 允许我们在后台线程中运行 JavaScript 代码,与主线程并行执行。将图片压缩任务交给 Web Worker,可以避免阻塞主线程,保证页面的流畅性。

整个图片压缩流程如下:

  1. 用户选择图片文件(通过 <input type="file">)。
  2. 主线程使用FileReder读取文件,并将其转为base64格式的图片数据
  3. 将base64格式的图片数据传递给 Web Worker。
  4. 在子线程中读取base64格式的图片数据并将其转为blob,然后使用createImageBitmap键blob转为bitmap位图
  5. Web Worker 使用 Canvas API 绘制图片并进行压缩(调整尺寸、降低质量)。
  6. 压缩完成后,将结果转为(Blob 或 Data URL)返回主线程。
  7. 主线程接收压缩结果并用于上传或展示。

核心实现代码

主线程代码(main.js)

// 创建 Web Worker 实例
const worker = new Worker('image-compressor.js');

// 监听文件选择事件
document.getElementById('fileInput').addEventListener('change', (event) => {
  const file = event.target.files[0];
  if (!file || !file.type.startsWith('image/')) return;

  // 读取文件为 Blob 或 Data URL
  const reader = new FileReader();
  reader.onload = (e) => {
    const imageDataUrl = e.target.result;

    // 向 Web Worker 发送图片数据和压缩配置
    worker.postMessage({
      imageDataUrl,
      maxWidth: 800,
      maxHeight: 600,
      quality: 0.8, // JPEG 质量
    });
  };
  reader.readAsDataURL(file);
});

// 接收 Web Worker 返回的压缩结果
worker.onmessage = (event) => {
  const { success, result, error } = event.data;

  if (success) {
    // 压缩成功,result 是 Blob 对象
    console.log('图片压缩成功');
    // 可以将 result 用于上传或预览
    const compressedUrl = URL.createObjectURL(result);
    document.getElementById('preview').src = compressedUrl;
  } else {
    console.error('压缩失败:', error);
  }
};

// 错误处理
worker.onerror = (error) => {
  console.error('Web Worker 错误:', error);
};

Web Worker 代码(image-compressor.js)

// Web Worker 内部执行
self.onmessage = async function (event) {
  const { imageDataUrl, maxWidth, maxHeight, quality } = event.data;

  try {
    // 创建 Image 对象
    const img = new Image();
    img.src = imageDataUrl;

    // 等待图片加载完成
    await new Promise((resolve, reject) => {
      img.onload = resolve;
      img.onerror = reject;
    });

    // 计算目标尺寸(保持宽高比)
    let { width, height } = img;
    if (width > maxWidth) {
      height = (height * maxWidth) / width;
      width = maxWidth;
    }
    if (height > maxHeight) {
      width = (width * maxHeight) / height;
      height = maxHeight;
    }

    // 创建 Canvas 并绘制图片
    const canvas = new OffscreenCanvas(width, height); // 使用 OffscreenCanvas 更高效
    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0, width, height);

    // 导出为 Blob(压缩为 JPEG)
    const blob = await canvas.convertToBlob({
      type: 'image/jpeg',
      quality: quality,
    });

    // 将压缩后的 Blob 发送回主线程
    self.postMessage({ success: true, result: blob }, [blob]);
  } catch (error) {
    self.postMessage({ success: false, error: error.message });
  }
};

注意:使用 OffscreenCanvas 可以在 Web Worker 中直接操作 Canvas,性能更优。如果浏览器不支持,可以退回到主线程创建 Canvas 并传递 ImageBitmap,但这会增加复杂性。

关键点解析

  1. OffscreenCanvas

    OffscreenCanvas 是专为 Web Worker 设计的 Canvas API,允许在 Worker 线程中直接进行图形绘制,无需与主线程交互,极大提升了性能。

  2. 跨线程数据传输

    使用 postMessage 传递数据时,对于大型 Blob 对象,建议使用 Transferable Objects(如 [blob]),这样数据所有权会被转移,避免昂贵的复制操作,提升性能。

  3. 图片质量与格式

    • canvas.toBlob()OffscreenCanvas.convertToBlob() 支持指定 typequality 参数。
    • 常用格式:image/jpeg(有损压缩,适合照片)、image/png(无损,适合图标)、image/webp(更优压缩比,但兼容性需注意)。
  4. 错误处理

    在 Worker 中捕获所有可能的错误(如图片加载失败、Canvas 操作异常),并通过 postMessage 将错误信息返回给主线程,保证程序健壮性。

优化建议

  1. 限制最大尺寸:防止用户上传超大图片导致内存溢出。
  2. 支持多种格式:根据原图格式或用户需求选择输出格式。
  3. 进度反馈:对于大图,可通过 postMessage 发送进度事件(如“加载中”、“压缩中”)。
  4. 内存管理:及时释放不再使用的 Blob URL(URL.revokeObjectURL())。
  5. 降级方案:对于不支持 Web Worker 或 OffscreenCanvas 的旧浏览器,可降级到主线程处理。

总结

通过结合 Web Worker 和 Canvas API,我们可以在不影响用户界面的前提下,高效地完成图片压缩任务。这种方案特别适用于需要处理多张图片或大尺寸图片的 Web 应用,如社交媒体、电商平台、在线编辑器等。掌握这一技术,能够显著提升应用的性能和用户体验。

提示:在实际项目中,还可以结合更专业的图像处理库(如 picabrowser-image-compression)在 Web Worker 中运行,以获得更高级的压缩算法和更好的效果。