一、需求
(1)点击图片 Icon,出现文件上传框,选择图片;
(2)验证图片类型及大小,本地预览的同时上传到服务器;
(3)上传完之后进行发送,同消息发送(本文不涉及)。
二、DOM节点预备
- 图片Icon(展示,用于点击)
- 文件上传标签
<input type="file" />
(隐藏)
可选项:
- form 表单(隐藏):如果不使用 formData,可通过表单上传
- iframe(隐藏):如果不使用 ajax,可用于存放图片上传之后返回的数据(被称为隐藏 iframe 模拟 Ajax 跨域上传,注意 form 的
target
要写 iframe 的name
属性)
render() {
return (
<div>
<span className="picture" onClick={this.handlePicClick}></span>
<iframe name="post_frame" style={{display: 'none'}} onLoad={this.loadIframe}></iframe>
<form action="/upload.action" method="post" target="post_frame" encType="multipart/form-data" ref={ node => this.imgForm= node }>
<input type="file"
name="imgUpload"
accept="image/gif, image/jpeg, image/png, image/bmp"
onChange={this.handleImgChange}
style={{display: 'none'}}
ref={node => this.imgUploader = node} />
</form>
</div>
);
}
三、操作实现
1. 点击 图片 Icon,触发文件上传标签,打开文件上传框:
handlePicClick = () => {
this.imgUploader.click(); // 相当于点击<input type="file />,调出文件框
}
2. 监听 <input type="file" />
的 onchange
事件,获取上传的文件内容:
handleImgChange(e) {
const { file, url } = this.getFile(e); // 获取文件内容
if (this.imgValidator(file)) { // 验证文件内容
this.uploadImg(file); // 本地预览的同时上传文件
}
// 允许多次上传同一张图片
e.target.value = '';
}
【注意】:如果没有最后一行代码,那么在重复上传同一张图片时,不会触发 onchange 事件,因此需要最后一行代码来保证能够允许多次上传同一张图片,解决方案来自:图片上传以及允许连续上传同一图片
3. 获取文件内容(路径,名称,类型,大小)
getFile(e) {
let fileEle = e.target;
let fileObj = null;
let filePath = '';
if (fileEle.files) {
fileObj = fileEle.files[0];
filePath = window.URL.createObjectURL(fileObj);
} else {
// 通过网搜各种兼容 IE9 的方法,尝试之后,没效果,果断放弃了。。。
// 深深地体会到了兼容 IE9 的绝望(╥╯^╰╥)
try {
fileEle.select();
fileEle.blur();
filePath = document.selection.createRange().text;
// filePath = fileEle.value; // 只在本地有效
let fileSystem = new ActiveXObject("Scripting.FileSystemObject");
if(fileSystem.FileExists(filePath)){
fileObj = fileSystem.GetFile(filePath);
/* console.info("文件类型:" + fileObj.type);
console.info("文件名称:" + fileObj.name);
console.info("文件大小:" + fileObj.size); */
}
} catch (e) {
console.log('GetFile Error:', e);
}
}
return {
file: fileObj,
url: filePath
};
}
非 IE9 下可以通过 e.target.files
来获取到选择的图片内容,然后生成相应的文件路径;而 IE9 获取不到 e.target.files
对象,需要特殊兼容下(谁曾想兼容之路漫漫,爬过一座山,又遇一道坎༼༎ຶᴗ༎ຶ༽)。
(1)对象 URL:性能比较好
对象 URL 也被称为 blob URL,指的是引用保存在 File 或 Blob 中数据的 URL。通过 window.URL.createObjectURL(file)
方法创建一个对象 URL,它的返回值是一个字符串,指向一块内存地址。因为这个字符串是 URL,因此能够在 DOM 中(如img标签)进行使用。
使用对象 URL 的好处是可以不必把文件内容读取到 JavaScript 中而直接引用文件内容。页面卸载时会自动释放对象 URL 占用的内存。
(2)FileReader
FileReader 实现的是一种异步文件读取机制。可以把 FileReader 想象成 XMLHttpRequest,区别只是它读取的是文件系统,而不是远程服务器。为了读取文件中的数据,FileReader 提供了4个方法,这里需要用到的方法如下:
-
readAsDataURL(file)
:读取文件并将文件以数据 URI 的形式保存在 result 属性中
由于读取过程是异步的,FileReader 也提供了几个事件,包括:progress
、error
、load
。
【测试代码】:
let file = e.target.files[0];
let reader = new FileReader();
reader.onload = (e) => {
let url = e.target.reasult;
// 可以构造img对象:
// let img = `<img src="${url}" className="upload-img" />`;
};
// 读取file对象并存放为data: URL格式的字符串
reader.readAsDataURL(file);
话说一张图片转成 base64 还是挺大的,不建议进行ws传输。
FileReader 的其它方法、progress
的信息(属性) 和error
事件的错误码,具体请参考《JavaScript高级程序设计》。
(3)IE9:在假设能获取到 input 中 text 的情况下(本地测试可以,联调环境各种问题),so,还是别在这上面浪费时间了╮(╯﹏╰)╭,跟产品求个绕,果断放弃 IE9
4. 验证文件内容
通过步骤 3,我们可以知道上传的图片文件所包含的信息,包括 路径(url)、名称(name)、类型(type)、大小(size),因此可以对这几项进行业务上的验证。
5. 上传至服务器
(1)form 表单上传
在不使用 formData 的情况下,form 表单上传直接调用 submit
方法即可:
this.imgForm.submit();
当数据返回之后,就是 iframe 派上用场的时候啦:
// 上传图片后返回结果处理
loadIframe(e) {
let imgInfo = this.localInfo; // 本地图片信息
let isUpload;
try {
let response = e.target.contentDocument.body.textContent;
if (!response) {
isUpload = false; // 上传失败
} else {
// 处理返回结果
// 如果上传成功,则获取新的 imgInfo
}
this.handleUploadRes(isUpload, imgInfo); // 处理图片结果
} catch(e) {
console.log('loadIframe Error:', e); // iframe内容读取受限,如浏览器兼容性问题
}
}
(2) formData 上传(IE9以下不支持)
创建 formData:
// 方法一:直接将 form 对象装载到 formData 中
let fd = new FormData(this.imgFrom);
// 方法二:选择性添加信息
let fd = new FormData();
fd.append('uploadImg', file);
发送 Ajax:
// 以下代码来自参考文章:[上传图片攻略全解],见文末参考
let xhr = new XMLHttpRequest();
xhr.open('POST','/upload.action');
xhr.onreadystatechange = function(){};
xhr.send(fd);
// 获取上传的进度
xhr.upload.onprogress = function(e){
var loaded = e.loaded;//已经上传大小情况
var tot = e.total; //附件总大小
var per = Math.floor(100*loaded/tot); //已经上传的百分比
}
// 或者使用框架的话:
/*ajax({
url: '/upload.action',
data: fd,
type: 'post',
success: () => {}
});*/
四、补充
- 考虑到真实的业务场景中是需要将图片进行发送的,这时候需要考虑到的情况是:如果图片过大,上传需要一定的时间,这时候就不能阻塞后续文本消息的发送。
- 论写
try...catch(e)...
的重要性,在Chrome浏览器影响不大,但是一旦调试 IE 的时候,就会明白这个语句有多重要了,它可以在产生错误的时候不让 IE 进入调试状态,也能直接在控制台看到错误结果,可以大大提高效率。
至此,聊天室的又一道难题解决啦~蟹蟹阅读哟!ヾ(✿゚▽゚)ノ