在Web开发中,用户从手机或相机选择的原始图片常常达到数兆字节,直接上传既浪费流量又加重服务器负担。借助JavaScript的File API与Canvas,我们可以完全在浏览器端完成图片压缩,再使用FormData提交到服务器。下面通过一个完整示例,分三个步骤说明具体实现方式。

第一步:获取用户选择的图片文件
我们使用一个隐藏或可见的<input type="file">元素,监听其change事件,从中拿到用户选择的File对象。File对象继承了Blob,包含了图片的原始二进制数据和类型信息。此时图片还没有被读取到内存中绘制,仅仅是得到了引用。
为了限制用户误操作,可以通过accept属性约束只能选图片,并通过multiple属性决定是否允许多张。拿到File后,我们不能直接把它画到Canvas上,而是要先生成一个临时的URL,或者改用FileReader读取为DataURL。实际项目中更推荐URL.createObjectURL,因为它不会把整个文件以 base64 形式塞进内存,性能更好。
<input type="file" id="fileInput" accept="image/*" />
<script>
const input = document.getElementById('fileInput');
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
console.log('原始大小', file.size, '类型', file.type);
// 下一步将用这个file进行压缩
});
</script>
第二步:使用Canvas进行图片压缩
将File对象转换为HTMLImageElement后,我们可以把图像绘制到指定尺寸的Canvas上。压缩的本质是:把大图按目标宽度等比缩放,绘制到较小的画布区域,再调用canvas.toBlob方法以指定图片格式和清晰度导出。通常JPEG格式配合0.7左右的quality参数,能在肉眼难以察觉的情况下大幅减小体积。
需要注意,iOS Safari对Canvas最大尺寸有限制,单边超过约4096像素就可能绘制失败。因此代码里应计算缩放比例,保证最长边不超过设定值。下面函数接收File和最大边长,返回压缩后的Blob,整个过程都是异步的,方便用await串联后续上传。
function compressImage(file, maxSide = 1000, quality = 0.7) {
return new Promise((resolve, reject) => {
const url = URL.createObjectURL(file);
const img = new Image();
img.onload = () => {
URL.revokeObjectURL(url);
let { width, height } = img;
if (width > height && width > maxSide) {
height = Math.round(height * maxSide / width);
width = maxSide;
} else if (height > maxSide) {
width = Math.round(width * maxSide / height);
height = maxSide;
}
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) => resolve(blob),
'image/jpeg',
quality
);
};
img.onerror = reject;
img.src = url;
});
}
第三步:通过FormData上传压缩后的图片
得到压缩Blob后,我们把它包装成新的File或直接作为Blob放进FormData。使用fetch或XMLHttpRequest发送到后端接口即可。由于Blob本身没有文件名,若服务端依赖filename字段,可以手动构造new File([blob], 'compressed.jpg', { type: 'image/jpeg' })。
相比直接传原图,压缩后体积通常只有原先的10%到30%,上传耗时显著下降。下面的示例演示了如何衔接前两步,并把结果提交到ipipp.com示例接口。注意实际项目中应加上错误处理与上传进度提示,提升体验。
async function handleUpload(file) {
try {
const compressedBlob = await compressImage(file, 1200, 0.75);
const formData = new FormData();
formData.append('image', compressedBlob, 'compressed.jpg');
const resp = await fetch('https://ipipp.com/api/upload', {
method: 'POST',
body: formData
});
if (!resp.ok) throw new Error('上传失败');
const data = await resp.json();
console.log('服务器返回', data);
} catch (err) {
console.error('处理过程出错', err);
}
}
常见问题与优化建议
第一,PNG图片含透明通道,用image/jpeg导出会丢失透明度并填充黑色背景。若业务必须保留透明,应判断原图类型,对PNG仅做尺寸缩放而不强转格式,或使用image/webp(需注意兼容性)。第二,批量上传时要避免同时创建多个超大Canvas,可改为队列串行压缩,防止移动端内存溢出。
第三,压缩参数应根据场景调整:头像类可更激进(质量0.6、边长400),摄影分享类则需保留细节(质量0.85、边长1600)。通过合理封装上述三步逻辑,就能在任意现代浏览器中稳定实现客户端图片压缩与上传,减轻服务端压力并改善用户等待时间。
JavaScript图片压缩File_API修改时间:2026-08-07 04:54:24