本文來自《FileAPI 文件操做實戰》,其餘系列放在了Githubjavascript
歡迎交流和Starcss
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>`;
}
複製代碼
仍是以圖片讀取爲例,讀取而且顯示全部的圖片類型文件。
文件讀取須要使用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);
複製代碼
後端相關超出了本文的討論範圍,能夠參考這篇文章。