前端上传图片

3,121 阅读2分钟

前端上传图片到服务器,一般会有两种方式:

1. Base64编码

2. FormData文件流

首先,如果图片转为 Base64 后,除了体积会增大33%左右外,更大的问题是,你以为你是在以字符串的格式传输二进制。 其实非也,尤其是你把它转为 JSON 传给后端,由于变成了需要解析的字符串,对后端的压力会陡增,你会明显看到处理速度的急剧下滑。以及,你还需要去调 nginx 配置文件以避免 nginx 觉得你字符串过长,直接报错 Entity Too Large 把包拒掉。

所以,Base64 更适合用来处理小图,比如头像或小图标之类,保存在本地,以减少 Http 的请求次数;而大图比较适合用 FormData 文件流来传输。


而我最近的需求是把手机拍照的图片上传到服务器,所以确定了用文件流来处理。

而我们的应用是经过 Hbuilder 打包后的 Web App, 加持了 iOS 和 Android 的原生 SDK 的功能,所以面临着两个选择:

  1. 使用 Web 原生的 <input> 来拍照及选取相册
  2. 使用 Hbuilder 的提供的 SDK

如果用原生 <input> ,可以这么写(项目用的是 Vue):

<input type="file" accept="image/*" capture="camera" @change="onChange" multiple>

实际上呢,坑还不少。。。

加上capture="camera"这个属性,是为了让手机可选相机,不过真实情况是:

  • 部分 Android 手机上调用不了相机,始终只能选相册;(解决办法:请教 Android 原生开发的童鞋吧)

  • 苹果手机上,只能拍照,不能选相册;(解决办法:判断 iOS,如果是 iOS 就去掉 capture 属性)

所以, Android 不能调用相机这点,就把 Web 原生的方案毙掉了,后来转用 HBuilder 的 SDK;


接下来的大致思路:

  1. 获取到 File 对象

  2. FileReader 转成 dataUrl

  3. canvas 接收 dataUrl

  4. 对图片进行宽高比的设置(压缩大小)

  5. canvas.toDataURL 将压缩后的图片生成新的 Base64 编码

  6. 执行回调函数,将新的 Base64 转换为文件

  7. 最后拿到文件,构造 FormData 对象上传至服务器。

特别要注意的是:

  • FormData 的请求方式,每个参数都需要通过 append 方法添加进去;

  • 不需要单独设置请求头的 Content-Type,系统自动会设定为 multipart/form-data


经过几天线上的验证,事实证明,只用文件流传图片,有时也会报 Entity too large 这个错误,后来经过定位发现在 canvas.toDataURL 时,最好用 jpeg 格式进行压缩,不要用 png 的无损压缩,这点非常关键!

---The End