本文來自 《FileAPI 文件操做實戰》其餘全部系列都放在了Github。歡迎交流和Star。javascript
HTML5 爲咱們提供了 File API 相關規範。主要涉及 File 接口 和 FileReader 對象 。html
本文整理了兼容性檢測、文件選擇、屬性讀取、文件讀取、進度監控、大文件分片上傳以及拖拽上傳等開發中常見的前端文件操做。前端
首先,咱們的 File 來自於<input>
標籤中選中的文件列表。因此,準備以下的 HTML 代碼:java
<input type="file" id="files" multiple /> <div id="list"></div> <div id="images"></div> <!-- File API相關操做寫在了script.js中 --> <script src="./script.js"></script>
File 對象是特殊類型的 Blob。在 script 入口處,應該檢測當前瀏覽器是否支持 File API:git
if (!(window.File && window.FileReader && window.FileList && window.Blob)) { throw new Error("當前瀏覽器對FileAPI的支持不完善"); }
對於 type 爲 file 類型的<input>
標籤,在選擇文件的時候,會觸發change
事件。用戶選中的文件信息也會傳入回調函數的第一個參數中。github
function handleFileSelect(event) { const { files } = event.target; if (!files.length) { console.log("沒有選擇文件"); return; } console.log("選中的文件信息是:", files); } document .querySelector("#files") .addEventListener("change", handleFileSelect, false);
event.target.files
是一個FileList
對象,它是一個由File
對象組成的列表。後端
每一個 File 對象,保存着選中的對應文件的屬性。經常使用的用:api
下面,經過 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( ` <li> <strong>${file.name}</strong> (${file.type || "n/a"}) - ${file.size} bytes </li> ` ); } document.querySelector("#list").innerHTML = `<ul>${innerHTML.join("")}</ul>`; }
仍是以圖片讀取爲例,讀取而且顯示全部的圖片類型文件。app
文件讀取須要使用FileReader
對象,它經常使用 3 個回調方法:
和Image
相似,在讀取文件以前,須要先綁定事件處理。它讀取操做有:readAsArrayBuffer、readAsDataURL、readAsBinaryString、readAsText。傳入的參數就是File
對象。
那麼這幾個方法有什麼區別呢?不一樣的讀取方式,回調事件onload
接受到的event.target.result
不相同。好比,readAsDataURL
讀取的話,result 是一個圖片的 url。
下面就是讀取圖片文件,而後展現的一個例子:
function handleFileSelect(event) { let { files } = event.target; if (!files.length) { return; } let vm = document.createDocumentFragment(), re = /image.*/, loaded = 0, // 完成加載的圖片數量 total = 0; // 總共圖片數量 // 統計image文件數量 for (let file of files) { re.test(file.type) && total++; } // onloadstart回調 const handleLoadStart = (ev, file) => console.log(`>>> Start load ${file.name}`); // onload回調 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); // 讀取文件對象 reader.readAsDataURL(file); } } document .querySelector("#files") .addEventListener("change", handleFileSelect, false);
在監控讀取進度的時候,主要是處理 FileReader 對象上的 onprogress
事件。
下面的例子,請打開一個較大的文件來查看效果(不然一下就讀取完了):
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; } // 計算進度,而且以百分比形式展現 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);
在對於超大文件,通常採用分片上傳的思路解決。文章開頭有講到,File 是 Blob 的一個特例。而 Blob 上有一個slice 方法,經過它,前端就能夠實現分片讀取大文件的操做。
爲了方便說明,請先準備好一個 txt 文件,文件內容就是:hello world
。
示例代碼以下,代碼中只讀取前 5 個字節,因爲每一個英文字母佔 1 個字節,因此打印結果應該是「hello」。
function handleFileSelect(event) { let { files } = event.target; if (!files.length) { return; } // 爲了方便說明,這裏僅僅讀取第一個文件 const file = files[0]; // 讀取前5個字節的內容 const blob = file.slice(0, 5); const reader = new FileReader(); // 控制檯輸出結果應該是:hello reader.onload = ev => console.log(ev.target.result); reader.readAsText(blob); } document .querySelector("#files") .addEventListener("change", handleFileSelect, false);
和前面所述的 File API 相關是徹底同樣的。惟一須要特殊處理的是文件對象的獲取入口改變了。對於<input>
標籤,監聽 onchange 事件,FileList 存放在 event.target.files 中;對於拖拽操做,FileList 存放在拖拽事件的回調函數參數裏,經過 event.dataTransfer.files 訪問便可。
須要修改一下 html 代碼:
<!DOCTYPE html> <head> <meta charset="UTF-8"> <style> #container { width: 300px; height: 300px; border: 3px dotted red; } </style> </head> <body> <div id="container"></div> <script src="./script.js"></script> </body> </html>
腳本文件的代碼以下:
function handleDropover(event) { event.stopPropagation(); event.preventDefault(); } function handleDrop(event) { event.stopPropagation(); event.preventDefault(); /***** 訪問拖拽文件 *****/ const files = event.dataTransfer.files; console.log(files); /**********/ } const target = document.querySelector("#container"); target.addEventListener("dragover", handleDropover); target.addEventListener("drop", handleDrop);
後端相關超出了本文的討論範圍,能夠參考這篇文章。