需求
公司现在在移动端使用webuploader实现图片上传,但最近需求太奇葩了,插件无法满足我们的PM
经过商讨决定下掉这个插件,使用H5原生的API实现图片上传。
7.3日发布:单张图片上传
9.29日更新:多张图片并发上传
11.06日更新:常见问题
效果图:
基础知识
上传图片这块有几个知识点要先了解的。首先是有几种常见的移动端图片上传方式:
FormData
通过FormData对象可以组装一组用 XMLHttpRequest发送请求的键/值对。它可以更灵活方便的发送表单数据,因为可以独立于表单使用。如果你把表单的编码类型设置为multipart/form-data ,则通过FormData传输的数据格式和表单通过submit() 方法传输的数据格式相同。
这是一种常见的移动端上传方式,FormData也是H5新增的 兼容性如下:
base64
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。 由于2的6次方等于64,所以每6个位元为一个单元,对应某个可打印字符。 三个字节有24个位元,对应于4个Base64单元,即3个字节可表示4个可打印字符。
base64可以说是很出名了,就是用一段字符串来描述一个二进制数据,所以很多时候也可以使用base64方式上传。兼容性如下:
还有一些对象需要了解:
Blob对象
一个 Blob对象表示一个不可变的, 原始数据的类似文件对象。Blob表示的数据不一定是一个JavaScript原生格式。 File 接口基于Blob,继承 blob功能并将其扩展为支持用户系统上的文件。
简单说Blob就是一个二进制对象,是原生支持的,兼容性如下:
FileReader对象
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
FileReader也就是将本地文件转换成base64格式的dataUrl。
图片上传思路
准备工作都做完了,那怎样用这些材料完成一件事情呢。
这里要强调的是,考虑到移动端流量很贵,所以有必要对大图片进行下压缩再上传。
图片压缩很简单,将图片用canvas
画出来,再使用canvas.toDataUrl
方法将图片转成base64格式。
所以图片上传思路大致是:
- 监听一个
input(type=‘file’)
的onchange
事件,这样获取到文件file
; - 将
file
转成dataUrl
; - 然后根据
dataUrl
利用canvas
绘制图片压缩,然后再转成新的dataUrl
; - 再把
dataUrl
转成Blob
; - 把
Blob
append
进FormData
中; -
xhr
实现上传。
手机兼容性问题
理想很丰满,现实很骨感。
实际上由于手机平台兼容性问题,上面这套流程并不能全都支持。
所以需要根据兼容性判断。
经过试验发现:
- 部分安卓微信浏览器无法触发
onchange
事件(第一步就特么遇到问题)
这其实安卓微信的一个遗留问题。 查看讨论 解决办法也很简单:input
标签<input type=“file" name="image" accept="image/gif, image/jpeg, image/png”>
要写成<input type="file" name="image" accept=“image/*”>
就没问题了。 - 部分安卓微信不支持
Blob
对象 - 部分
Blob
对象append
进FormData
中出现问题 - iOS 8不支持
new File Constructor
,但是支持input
里的file
对象。 - iOS 上经过压缩后的图片可以上传成功 但是size是0 无法打开。
- 部分手机出现图片上传转换问题,请移步。
- 安卓手机不支持多选,原因在于multiple属性根本就不支持。
- 多张图片转base64时候卡顿,因为调用了cpu进行了计算。
- 上传图片可以使用base64上传或者formData上传
上传思路修改方案
经过考虑,我们决定做兼容性处理:
这里边两条路,最后都是File
对象append
进FormData
中实现上传。
代码实现
首先有个html
<input type="file" name="image" accept=“image/*” onchange='handleInputChange'>
然后js如下:
// 全局对象,不同function使用传递数据
const imgFile = {};
function handleInputChange (event) {
// 获取当前选中的文件
const file = event.target.files[0];
const imgMasSize = 1024 * 1024 * 10; // 10MB
// 检查文件类型
if(['jpeg', 'png', 'gif', 'jpg'].indexOf(file.type.split("/")[1]) < 0){
// 自定义报错方式
// Toast.error("文件类型仅支持 jpeg/png/gif!", 2000, undefined, false);
return;
}
// 文件大小限制
if(file.size > imgMasSize ) {
// 文件大小自定义限制
// Toast.error("文件大小不能超过10MB!", 2000, undefined, false);
return;
}
// 判断是否是ios
if(!!window.navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)){