摘要:準(zhǔn)備工作首先,我們的來自于標(biāo)簽中選中的文件列表。用戶選中的文件信息也會(huì)傳入回調(diào)函數(shù)的第一個(gè)參數(shù)中。唯一需要特殊處理的是文件對(duì)象的獲取入口改變了。對(duì)于標(biāo)簽,監(jiān)聽事件,存放在中對(duì)于拖拽操作,存放在拖拽事件的回調(diào)函數(shù)參數(shù)里,通過訪問即可。
本文來自《FileAPI 文件操作實(shí)戰(zhàn)》介紹其他所有系列都放在了Github。歡迎交流和Star。
HTML5 為我們提供了 File API 相關(guān)規(guī)范。主要涉及 File 接口 和 FileReader 對(duì)象 。
本文整理了兼容性檢測(cè)、文件選擇、屬性讀取、文件讀取、進(jìn)度監(jiān)控、大文件分片上傳以及拖拽上傳等開發(fā)中常見的前端文件操作。
準(zhǔn)備工作首先,我們的 File 來自于標(biāo)簽中選中的文件列表。所以,準(zhǔn)備如下的 HTML 代碼:
檢測(cè)兼容性
File 對(duì)象是特殊類型的 Blob。在 script 入口處,應(yīng)該檢測(cè)當(dāng)前瀏覽器是否支持 File API:
if (!(window.File && window.FileReader && window.FileList && window.Blob)) { throw new Error("當(dāng)前瀏覽器對(duì)FileAPI的支持不完善"); }監(jiān)聽文件選擇
對(duì)于 type 為 file 類型的標(biāo)簽,在選擇文件的時(shí)候,會(huì)觸發(fā)change事件。用戶選中的文件信息也會(huì)傳入回調(diào)函數(shù)的第一個(gè)參數(shù)中。
function handleFileSelect(event) { const { files } = event.target; if (!files.length) { console.log("沒有選擇文件"); return; } console.log("選中的文件信息是:", files); } document .querySelector("#files") .addEventListener("change", handleFileSelect, false);文件屬性-File
event.target.files 是一個(gè)FileList對(duì)象,它是一個(gè)由File對(duì)象組成的列表。
每個(gè) File 對(duì)象,保存著選中的對(duì)應(yīng)文件的屬性。常用的用:
name:文件名
type:文件類型
size:文件大小
下面,通過 type 屬性,過濾掉非圖片類型的文件,只展示圖片類型文件的信息:
function handleFileSelect(event) { const { files } = event.target; if (!files.length) { console.log("沒有選擇文件"); return; } const innerHTML = []; const reImage = /image.*/; for (let file of files) { if (!reImage.test(file.type)) { continue; } innerHTML.push( `
還是以圖片讀取為例,讀取并且顯示所有的圖片類型文件。
文件讀取需要使用FileReader對(duì)象,它常用 3 個(gè)回調(diào)方法:
onload: 文件讀取完成
onloadstart:文件上傳開始
onprogress : 文件上傳中觸發(fā)
和Image類似,在讀取文件之前,需要先綁定事件處理。它讀取操作有:readAsArrayBuffer、readAsDataURL、readAsBinaryString、readAsText。傳入的參數(shù)就是File對(duì)象。
那么這幾個(gè)方法有什么區(qū)別呢?不同的讀取方式,回調(diào)事件onload接受到的event.target.result不相同。比如,readAsDataURL讀取的話,result 是一個(gè)圖片的 url。
下面就是讀取圖片文件,然后展示的一個(gè)例子:
function handleFileSelect(event) { let { files } = event.target; if (!files.length) { return; } let vm = document.createDocumentFragment(), re = /image.*/, loaded = 0, // 完成加載的圖片數(shù)量 total = 0; // 總共圖片數(shù)量 // 統(tǒng)計(jì)image文件數(shù)量 for (let file of files) { re.test(file.type) && total++; } // onloadstart回調(diào) const handleLoadStart = (ev, file) => console.log(`>>> Start load ${file.name}`); // onload回調(diào) const handleOnload = (ev, file) => { console.log(`<<< End load ${file.name}`); const img = document.createElement("img"); img.height = 250; img.width = 250; img.src = ev.target.result; vm.appendChild(img); // 完成加載后,將其放入dom元素中 if (++loaded === total) { document.querySelector("#images").appendChild(vm); } }; for (let file of files) { if (!re.test(file.type)) { continue; } const reader = new FileReader(); reader.onloadstart = ev => handleLoadStart(ev, file); reader.onload = ev => handleOnload(ev, file); // 讀取文件對(duì)象 reader.readAsDataURL(file); } } document .querySelector("#files") .addEventListener("change", handleFileSelect, false);監(jiān)控讀取進(jìn)度
在監(jiān)控讀取進(jìn)度的時(shí)候,主要是處理 FileReader 對(duì)象上的 onprogress 事件。
下面的例子,請(qǐng)打開一個(gè)較大的文件來查看效果(否則一下就讀取完了):
function handleFileSelect(event) { let { files } = event.target; if (!files.length) { return; } const handleLoadStart = (ev, file) => console.log(`>>> Start load ${file.name}`); const handleProgress = (ev, file) => { if (!ev.lengthComputable) { return; } // 計(jì)算進(jìn)度,并且以百分比形式展示 const percent = Math.round((ev.loaded / ev.total) * 100); console.log(`<<< Loding ${file.name}, progress is ${percent}%`); }; for (let file of files) { const reader = new FileReader(); reader.onloadstart = ev => handleLoadStart(ev, file); reader.onprogress = ev => handleProgress(ev, file); reader.readAsArrayBuffer(file); } } document .querySelector("#files") .addEventListener("change", handleFileSelect, false);大文件分片讀取
在對(duì)于超大文件,一般采用分片上傳的思路解決。文章開頭有講到,F(xiàn)ile 是 Blob 的一個(gè)特例。而 Blob 上有一個(gè)slice 方法,通過它,前端就可以實(shí)現(xiàn)分片讀取大文件的操作。
為了方便說明,請(qǐng)先準(zhǔn)備好一個(gè) txt 文件,文件內(nèi)容就是:hello world。
示例代碼如下,代碼中只讀取前 5 個(gè)字節(jié),由于每個(gè)英文字母占 1 個(gè)字節(jié),所以打印結(jié)果應(yīng)該是“hello”。
function handleFileSelect(event) { let { files } = event.target; if (!files.length) { return; } // 為了方便說明,這里僅僅讀取第一個(gè)文件 const file = files[0]; // 讀取前5個(gè)字節(jié)的內(nèi)容 const blob = file.slice(0, 5); const reader = new FileReader(); // 控制臺(tái)輸出結(jié)果應(yīng)該是:hello reader.onload = ev => console.log(ev.target.result); reader.readAsText(blob); } document .querySelector("#files") .addEventListener("change", handleFileSelect, false);拖拽上傳
和前面所述的 File API 相關(guān)是完全一樣的。唯一需要特殊處理的是文件對(duì)象的獲取入口改變了。對(duì)于標(biāo)簽,監(jiān)聽 onchange 事件,F(xiàn)ileList 存放在 event.target.files 中;對(duì)于拖拽操作,F(xiàn)ileList 存放在拖拽事件的回調(diào)函數(shù)參數(shù)里,通過 event.dataTransfer.files 訪問即可。
需要修改一下 html 代碼: