一岳守、需求
(1)點(diǎn)擊圖片 Icon,出現(xiàn)文件上傳框碌冶,選擇圖片湿痢;
(2)驗(yàn)證圖片類型及大小,本地預(yù)覽的同時(shí)上傳到服務(wù)器扑庞;
(3)上傳完之后進(jìn)行發(fā)送譬重,同消息發(fā)送(本文不涉及)。
二罐氨、DOM節(jié)點(diǎn)預(yù)備
- 圖片Icon(展示臀规,用于點(diǎn)擊)
- 文件上傳標(biāo)簽
<input type="file" />
(隱藏)
可選項(xiàng):
- form 表單(隱藏):如果不使用 formData,可通過(guò)表單上傳
- iframe(隱藏):如果不使用 ajax栅隐,可用于存放圖片上傳之后返回的數(shù)據(jù)(被稱為隱藏 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>
);
}
三、操作實(shí)現(xiàn)
1. 點(diǎn)擊 圖片 Icon约啊,觸發(fā)文件上傳標(biāo)簽,打開文件上傳框:
handlePicClick = () => {
this.imgUploader.click(); // 相當(dāng)于點(diǎn)擊<input type="file />佣赖,調(diào)出文件框
}
2. 監(jiān)聽 <input type="file" />
的 onchange
事件恰矩,獲取上傳的文件內(nèi)容:
handleImgChange(e) {
const { file, url } = this.getFile(e); // 獲取文件內(nèi)容
if (this.imgValidator(file)) { // 驗(yàn)證文件內(nèi)容
this.uploadImg(file); // 本地預(yù)覽的同時(shí)上傳文件
}
// 允許多次上傳同一張圖片
e.target.value = '';
}
【注意】:如果沒(méi)有最后一行代碼,那么在重復(fù)上傳同一張圖片時(shí)憎蛤,不會(huì)觸發(fā) onchange 事件外傅,因此需要最后一行代碼來(lái)保證能夠允許多次上傳同一張圖片纪吮,解決方案來(lái)自:圖片上傳以及允許連續(xù)上傳同一圖片
3. 獲取文件內(nèi)容(路徑,名稱萎胰,類型碾盟,大小)
getFile(e) {
let fileEle = e.target;
let fileObj = null;
let filePath = '';
if (fileEle.files) {
fileObj = fileEle.files[0];
filePath = window.URL.createObjectURL(fileObj);
} else {
// 通過(guò)網(wǎng)搜各種兼容 IE9 的方法技竟,嘗試之后冰肴,沒(méi)效果,果斷放棄了榔组。熙尉。。
// 深深地體會(huì)到了兼容 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 下可以通過(guò) e.target.files
來(lái)獲取到選擇的圖片內(nèi)容搓扯,然后生成相應(yīng)的文件路徑检痰;而 IE9 獲取不到 e.target.files
對(duì)象,需要特殊兼容下(誰(shuí)曾想兼容之路漫漫锨推,爬過(guò)一座山铅歼,又遇一道坎???????)。
(1)對(duì)象 URL:性能比較好
對(duì)象 URL 也被稱為 blob URL换可,指的是引用保存在 File 或 Blob 中數(shù)據(jù)的 URL椎椰。通過(guò) window.URL.createObjectURL(file)
方法創(chuàng)建一個(gè)對(duì)象 URL,它的返回值是一個(gè)字符串锦担,指向一塊內(nèi)存地址俭识。因?yàn)檫@個(gè)字符串是 URL,因此能夠在 DOM 中(如img標(biāo)簽)進(jìn)行使用洞渔。
使用對(duì)象 URL 的好處是可以不必把文件內(nèi)容讀取到 JavaScript 中而直接引用文件內(nèi)容套媚。頁(yè)面卸載時(shí)會(huì)自動(dòng)釋放對(duì)象 URL 占用的內(nèi)存。
(2)FileReader
FileReader 實(shí)現(xiàn)的是一種異步文件讀取機(jī)制磁椒〉塘觯可以把 FileReader 想象成 XMLHttpRequest,區(qū)別只是它讀取的是文件系統(tǒng)浆熔,而不是遠(yuǎn)程服務(wù)器本辐。為了讀取文件中的數(shù)據(jù),F(xiàn)ileReader 提供了4個(gè)方法医增,這里需要用到的方法如下:
-
readAsDataURL(file)
:讀取文件并將文件以數(shù)據(jù) URI 的形式保存在 result 屬性中
由于讀取過(guò)程是異步的慎皱,F(xiàn)ileReader 也提供了幾個(gè)事件,包括:progress
叶骨、error
茫多、load
。
【測(cè)試代碼】:
let file = e.target.files[0];
let reader = new FileReader();
reader.onload = (e) => {
let url = e.target.reasult;
// 可以構(gòu)造img對(duì)象:
// let img = `<img src="${url}" className="upload-img" />`;
};
// 讀取file對(duì)象并存放為data: URL格式的字符串
reader.readAsDataURL(file);
話說(shuō)一張圖片轉(zhuǎn)成 base64 還是挺大的忽刽,不建議進(jìn)行ws傳輸天揖。
FileReader 的其它方法夺欲、progress
的信息(屬性) 和error
事件的錯(cuò)誤碼,具體請(qǐng)參考《JavaScript高級(jí)程序設(shè)計(jì)》今膊。
(3)IE9:在假設(shè)能獲取到 input 中 text 的情況下(本地測(cè)試可以些阅,聯(lián)調(diào)環(huán)境各種問(wèn)題),so斑唬,還是別在這上面浪費(fèi)時(shí)間了╮(╯﹏╰)╭市埋,跟產(chǎn)品求個(gè)繞,果斷放棄 IE9
4. 驗(yàn)證文件內(nèi)容
通過(guò)步驟 3赖钞,我們可以知道上傳的圖片文件所包含的信息腰素,包括 路徑(url)、名稱(name)雪营、類型(type)弓千、大小(size)献起,因此可以對(duì)這幾項(xiàng)進(jìn)行業(yè)務(wù)上的驗(yàn)證洋访。
5. 上傳至服務(wù)器
(1)form 表單上傳
在不使用 formData 的情況下,form 表單上傳直接調(diào)用 submit
方法即可:
this.imgForm.submit();
當(dāng)數(shù)據(jù)返回之后谴餐,就是 iframe 派上用場(chǎng)的時(shí)候啦:
// 上傳圖片后返回結(jié)果處理
loadIframe(e) {
let imgInfo = this.localInfo; // 本地圖片信息
let isUpload;
try {
let response = e.target.contentDocument.body.textContent;
if (!response) {
isUpload = false; // 上傳失敗
} else {
// 處理返回結(jié)果
// 如果上傳成功姻政,則獲取新的 imgInfo
}
this.handleUploadRes(isUpload, imgInfo); // 處理圖片結(jié)果
} catch(e) {
console.log('loadIframe Error:', e); // iframe內(nèi)容讀取受限,如瀏覽器兼容性問(wèn)題
}
}
(2) formData 上傳(IE9以下不支持)
創(chuàng)建 formData:
// 方法一:直接將 form 對(duì)象裝載到 formData 中
let fd = new FormData(this.imgFrom);
// 方法二:選擇性添加信息
let fd = new FormData();
fd.append('uploadImg', file);
發(fā)送 Ajax:
// 以下代碼來(lái)自參考文章:[上傳圖片攻略全解]岂嗓,見文末參考
let xhr = new XMLHttpRequest();
xhr.open('POST','/upload.action');
xhr.onreadystatechange = function(){};
xhr.send(fd);
// 獲取上傳的進(jìn)度
xhr.upload.onprogress = function(e){
var loaded = e.loaded;//已經(jīng)上傳大小情況
var tot = e.total; //附件總大小
var per = Math.floor(100*loaded/tot); //已經(jīng)上傳的百分比
}
// 或者使用框架的話:
/*ajax({
url: '/upload.action',
data: fd,
type: 'post',
success: () => {}
});*/
四汁展、補(bǔ)充
- 考慮到真實(shí)的業(yè)務(wù)場(chǎng)景中是需要將圖片進(jìn)行發(fā)送的,這時(shí)候需要考慮到的情況是:如果圖片過(guò)大厌殉,上傳需要一定的時(shí)間食绿,這時(shí)候就不能阻塞后續(xù)文本消息的發(fā)送。
- 論寫
try...catch(e)...
的重要性公罕,在Chrome瀏覽器影響不大器紧,但是一旦調(diào)試 IE 的時(shí)候,就會(huì)明白這個(gè)語(yǔ)句有多重要了楼眷,它可以在產(chǎn)生錯(cuò)誤的時(shí)候不讓 IE 進(jìn)入調(diào)試狀態(tài)铲汪,也能直接在控制臺(tái)看到錯(cuò)誤結(jié)果,可以大大提高效率罐柳。
至此掌腰,聊天室的又一道難題解決啦~蟹蟹閱讀喲!ヾ(??▽?)ノ