接了个图片问答的需求:用户传张图,AI 看图回答。听着就是个 <input type=file>,真做下去才知道前端要替用户兜多少事——几十兆的原图直接传,请求超时;HEIC 格式后端不认;预览图把内存吃爆。一篇实战,把上传这条链路的脏活拆开讲。
先压,别让用户传原图
现在手机随手一拍就是十几兆,原图传上去既慢又烧带宽,多模态模型其实也不需要那么高分辨率。前端用 canvas 等比缩到长边 1568px 左右(多数视觉模型的友好尺寸)再传,体积能砍九成:
async function compress(file: File, maxEdge = 1568, quality = 0.82): Promise<Blob> {
const bitmap = await createImageBitmap(file);
const scale = Math.min(1, maxEdge / Math.max(bitmap.width, bitmap.height));
const w = Math.round(bitmap.width * scale);
const h = Math.round(bitmap.height * scale);
const canvas = new OffscreenCanvas(w, h);
const ctx = canvas.getContext('2d')!;
ctx.drawImage(bitmap, 0, 0, w, h);
bitmap.close(); // 重要,及时释放,否则内存涨得吓人
return canvas.convertToBlob({ type: 'image/jpeg', quality });
}
bitmap.close() 那行别漏。我做批量上传时没释放,连传十几张内存直接飙到几百兆,移动端 webview 当场崩。OffscreenCanvas 还能放进 Web Worker 压,不卡主线程,长列表上传体验好很多。
HEIC 是个大坑
iPhone 默认存 HEIC,浏览器 <img> 和 canvas 大多不认,createImageBitmap 直接抛错。我一开始没考虑,苹果用户上传全军覆没,测试机是安卓还没测出来,上线才暴雷。
处理方式:检测到 HEIC 先用 heic2any 之类转成 jpeg 再走压缩。代价是这个库 gzip 后小两百 KB,我做了动态 import,只有真遇到 HEIC 才加载,别让所有用户为少数格式买单:
async function normalize(file: File): Promise<File> {
if (/heic|heif/i.test(file.type) || /.heic$/i.test(file.name)) {
const { default: heic2any } = await import('heic2any'); // 按需加载
const blob = await heic2any({ blob: file, toType: 'image/jpeg' });
return new File([blob as Blob], file.name.replace(/.heic$/i, '.jpg'), { type: 'image/jpeg' });
}
return file;
}
预览图用 objectURL,记得 revoke
预览别用 FileReader 读 base64——大图转 base64 又慢又占内存,还会把 DOM 撑大。用 URL.createObjectURL 几乎零成本,但必须配对 revoke,否则这块内存到页面关都不释放:
const url = URL.createObjectURL(file);
previewEl.src = url;
previewEl.onload = () => URL.revokeObjectURL(url); // 加载完立刻回收
删除某张待传图片时也要 revoke 对应的 url。我有个版本忘了在删除分支里回收,用户反复添加删除几十次后,内存只涨不跌,典型的泄漏。
上传前的体面校验
别等传到后端才报「格式不支持」。前端先把住门——类型白名单、单张大小上限、张数上限,错误就地提示,省一次往返也省用户的脾气:
function validate(file: File) {
const ok = ['image/jpeg', 'image/png', 'image/webp', 'image/heic'];
if (!ok.includes(file.type)) return '只支持 JPG/PNG/WebP/HEIC';
if (file.size > 20 * 1024 * 1024) return '单张不能超过 20MB';
return null;
}
一个没做完美的
压缩质量我固定在 0.82,对照片够用,但用户传的若是带细密文字的截图,压完字会糊,影响模型识别。理想做法是按内容自适应质量,我评估了下投入产出比,暂时没做,先在文案里提示「文字截图建议原图」绕过去。算个偷懒,但当下够用。
多模态那头的模型能力我直接接讯飞 这类 MaaS 的现成 API,不操心算力和模型怎么部署,前端只把图片处理这条链路打磨干净。
你们上传链路还有啥隐形坑?HEIC 之外我估计还有冷门格式,评论区补补刀。