前端上传图片到服务器,一般会有两种方式:
1. Base64编码
2. FormData文件流
首先,如果图片转为 Base64 后,除了体积会增大33%左右外,更大的问题是,你以为你是在以字符串的格式传输二进制。 其实非也,尤其是你把它转为 JSON 传给后端,由于变成了需要解析的字符串,对后端的压力会陡增,你会明显看到处理速度的急剧下滑。以及,你还需要去调 nginx 配置文件以避免 nginx 觉得你字符串过长,直接报错 Entity Too Large
把包拒掉。
所以,Base64 更适合用来处理小图,比如头像或小图标之类,保存在本地,以减少 Http 的请求次数;而大图比较适合用 FormData 文件流来传输。
而我最近的需求是把手机拍照的图片上传到服务器,所以确定了用文件流来处理。
而我们的应用是经过 Hbuilder 打包后的 Web App, 加持了 iOS 和 Android 的原生 SDK 的功能,所以面临着两个选择:
- 使用 Web 原生的
<input>
来拍照及选取相册 - 使用 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;
接下来的大致思路:
-
获取到 File 对象
-
FileReader 转成 dataUrl
-
canvas 接收 dataUrl
-
对图片进行宽高比的设置(压缩大小)
-
再
canvas.toDataURL
将压缩后的图片生成新的 Base64 编码 -
执行回调函数,将新的 Base64 转换为文件
-
最后拿到文件,构造 FormData 对象上传至服务器。
特别要注意的是:
-
FormData 的请求方式,每个参数都需要通过 append 方法添加进去;
-
不需要单独设置请求头的
Content-Type
,系统自动会设定为multipart/form-data
经过几天线上的验证,事实证明,只用文件流传图片,有时也会报 Entity too large
这个错误,后来经过定位发现在 canvas.toDataURL
时,最好用 jpeg 格式进行压缩,不要用 png 的无损压缩,这点非常关键!
---The End