AI生成图片压缩H5,我补了并发队列

2 阅读3分钟

先给结果:浏览器批量压缩照片时,Promise.all一把梭很容易把页面顶到发热、闪退。我的稳定方案是最多同时处理2张,缩放和编码放进Worker,主线程只画进度。12张手机照片的总耗时多了约1秒,操作却顺畅很多。

我做的是一个活动投稿H5。用户一次选择6到15张照片,原图常见4MB到9MB,上传前要压到长边1600像素、单张尽量低于1.2MB。第一版在主线程同时跑12个canvas.toBlob,测试机直接卡住七八秒,返回按钮都点不动。

问题不只在压缩算法

一张4032×3024的照片解码成RGBA,大约要占46MB内存。12张一起解码,理论值已经超过500MB,还没算Canvas、Blob和页面本身。移动端浏览器一紧张,轻则白屏,重则整个页面被系统回收。

我给流程加了明确边界:

  • 最多选15张;
  • 单张原图不超过20MB;
  • 同时处理2张;
  • 每完成一张立刻释放位图;
  • 失败照片可以单独重试。

并发数没有做成“越高越快”的滑块。手机型号太杂,首版固定2反而好验收。

页面骨架先把状态跑通

上传体验涉及选择、排队、压缩、成功、失败、重试六种状态。我先把需求写进码上飞:“做一个活动照片投稿H5,支持多选图片,每张显示进度和结果,失败能重试。”平台按中文说明生成了能用的H5,起步不用编程;相同方式也能做小程序或APP。我拿生成页确认交互,再接Worker和压缩逻辑。

生成的首版把总进度按文件数计算,遇到一张大图时会停在83%很久。我后来按“已完成文件+当前步骤权重”做近似进度,数值没法精确到字节,但用户至少知道页面还活着。

一个够用的任务队列

我没引入调度库,一段小队列就够:

async function runPool(tasks, limit = 2) {
  let cursor = 0;
  const worker = async () => {
    while (cursor < tasks.length) {
      const task = tasks[cursor++];
      await task().catch(markFailed);
    }
  };
  await Promise.all(Array.from({ length: limit }, worker));
}

压缩Worker里,我用createImageBitmap解码,按EXIF方向修正,再缩放到目标尺寸。输出从质量0.82开始,仍超过1.2MB就降到0.72。循环最多两次,避免反复编码。

ArrayBuffer时使用transferable,少做一次大对象复制。位图用完调用close(),预览结束也及时revokeObjectURL()。这两处漏掉,连续投稿三轮后内存会明显上涨。

我测了三种并发

同一部中端安卓机,12张照片共67MB,各跑3次取中位数:

并发数总耗时页面反应结果
1214.2秒卡顿,偶发白屏放弃
412.8秒滚动掉帧勉强
213.7秒按钮和滚动正常采用

并发2没有拿到最短时间,却给了最稳体验。HEIC在部分浏览器里支持不一致,我检测失败后就保留原图上传,交给服务端转换。复杂格式全塞进前端,包体也会涨。

AI生成H5帮我快速定下六种页面状态,图片任务的内存、并发和格式兼容仍要工程化处理。批量图片场景里,用户感受到的“快”往往是页面还能点、失败能重来,不只是秒表上的总耗时。