在 HTML5 File API 出現之前,前端對於文件的操作是非常有局限性的,大多需要配合后端實現。出於安全角度考慮,從本地上傳文件時,代碼不可能獲取文件在用戶本地的地址,所以純前端不可能完成一些類似圖片預覽的功能。但是 File API 的出現,讓這一切變成了可能。
1、FileList 對象
FileList 對象針對表單的 file 控件。當用戶通過 file 控件選取文件后,這個控件的 files 屬性值就是 FileList 對象。它在結構上類似於數組,包含用戶選取的多個文件。如果 file 控件沒有設置 multiple 屬性,那么用戶只能選擇一個文件,FileList 對象也就只有一個元素了。
比如我選擇了兩個文件,控制台打印:
FileList {0: File, 1: File, length: 2} 0: File 1: File length:2 __proto__: Object
一般來說,我們不可能手動構造 FileList 對象,只能被動地讀取,也就是說只有用戶主動觸發了文件讀取行為,js 才能訪問到 FileList,而這通常發生在表單選擇文件或者拖拽文件中。
2、File對象
一個 FileList 對象包含了我們選中的 File 對象,那么一個 File 又有哪些屬性呢?
- name:文件名,該屬性只讀。
- size:文件大小,單位為字節,該屬性只讀。
- type:文件的 MIME 類型,如果分辨不出類型,則為空字符串,該屬性只讀。
- lastModified:文件的上次修改時間,格式為時間戳。
- lastModifiedDate:文件的上次修改時間,格式為 Date 對象實例。
我們可以選一個文件,自己打印出來看看。
3、Blob
File 對象是繼承自 Blob 對象的,Blob 又是什么鬼?
Blob(Binary Large Object)對象代表了一段二進制數據,提供了一系列操作接口。其他操作二進制數據的 API(比如 File 對象),都是建立在 Blob 對象基礎上的,繼承了它的屬性和方法。
生成 Blob 對象有兩種方法:一種是使用 Blob 構造函數,另一種是對現有的 Blob 對象使用 slice 方法切出一部分。
(1)Blob 構造函數,接受兩個參數。第一個參數是一個包含實際數據的數組,第二個參數是數據的類型,這兩個參數都不是必需的。
(2)Blob 對象的 slice 方法,將二進制數據按照字節分塊,返回一個新的 Blob 對象。
var a = ["hello", "world"]; var myBlob = new Blob(a, { "type" : "text/xml" }); var newBlob = myBlob.slice(0, 5); console.log(newBlob);
Blob 對象有兩個只讀屬性:
- size:二進制數據的大小,單位為字節。(文件上傳時可以在前端判斷文件大小是否合適)
- type:二進制數據的 MIME 類型,全部為小寫,如果類型未知,則該值為空字符串。(文件上傳時可以在前端判斷文件類型是否合適)
4、FileReader
FileReader API 才是我們接下去完成一些任務的關鍵。FileReader API 用於讀取文件,即把文件內容讀入內存。它的參數是 File 對象或 Blob 對象。
對於不同類型的文件,FileReader 提供不同的方法讀取文件。
- readAsText(Blob|File, opt_encoding):返回文本字符串。默認情況下,文本編碼格式是 UTF-8,可以通過可選的格式參數,指定其他編碼格式的文本。
- readAsDataURL(Blob|File):返回一個基於 Base64 編碼的 data-uri 對象。
- readAsArrayBuffer(Blob|File):返回一個 ArrayBuffer 對象。
除了以上三種不同的讀取文件方法,FileReader API 還有一個 abort 方法,用於中止文件上傳。
var reader = new FileReader(); reader.abort();
FileReader 對象采用異步方式讀取文件,可以為一系列事件指定回調函數。
- onabort 方法:讀取中斷或調用 reader.abort() 方法時觸發。
- onerror 方法:讀取出錯時觸發。
- onload 方法:讀取成功后觸發。
- onloadend 方法:讀取完成后觸發,不管是否成功。觸發順序排在 onload 或 onerror 后面。
- onloadstart 方法:讀取將要開始時觸發。
- onprogress 方法:讀取過程中周期性觸發。(可以用來獲取文件讀取的進度)
獲取到了文件的 base64 編碼,做一些諸如圖片預覽的功能,也就手到擒來了,有興趣的可以自己嘗試下,類似的還有文字預覽啊,等等。
5、URL
還有個強大的東西——URL 對象!
調用 URL 對象的 createObjectURL 方法,傳入一個 File 對象或者 Blob 對象,能生成一個鏈接。
var objecturl = window.URL.createObjectURL(blob);
上面的代碼會對二進制數據生成一個 URL,這個 URL 可以放置於任何通常可以放置 URL 的地方,比如 img 標簽的 src 屬性。需要注意的是,即使是同樣的二進制數據,每調用一次 URL.createObjectURL 方法,就會得到一個不一樣的 URL。這個 URL 的存在時間,等同於網頁的存在時間,一旦網頁刷新或卸載,這個 URL 就失效。(File 和 Blob 又何嘗不是這樣呢)除此之外,也可以手動調用 URL.revokeObjectURL 方法,使 URL 失效。
對於 File 或者 Blob 對象,我們可以這樣理解,它們的存在,依賴於頁面,而 URL 能給這些 "轉瞬即逝" 的二進制對象一個臨時的指向地址。這個臨時的地址還有什么用呢?也能做圖片預覽,相比前面用 readAsDataURL 的實現,更簡單了。
<input type='file' multiple /><br/>
<img />
<script> document.querySelector("input").onchange = function() { var files = this.files; document.querySelector("img").src = window.URL.createObjectURL(files[0]); } </script>
6、Canvas & dataURL & Blob
canvas 中有 toDataURL 函數,可以將 canvas 轉為 dataURL 形式的 base64 編碼,而 Blob 也可以轉為 dataURL,這三者之間是否可以互相轉換?有沒有什么實用之處?
(1)canvas -> dataURL
用 toDataURL 方法,比較簡單,不多說。
(2)blob -> dataURL
用 FileReader 的 readAsDataURL 方法
(3)dataURL -> blob
這個函數有點屌
function dataURLtoBlob(dataurl) { var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while(n--){ u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], {type:mime}); }
(4)dataURL - canvas
將 image 的 src 屬性置為 dataURL,再用 drawImage 方法畫上去。
(5)blob - canvas
如何把二進制形式的圖片畫上 canvas?先用 readAsDataURL 轉為 dataURL,接着就是 (4) 的事情了。
(6)canvas - blob
canvas 有原生的 toBlob 方法,使得圖片文件可以被緩存或保存到本地
canvas 轉為 blob 也可以用 dataURL 做跳板,先將 canvas 轉為 dataURL(1),再用 dataURL 轉為 blob(3)。
利用它們之間的轉換可以做些什么好玩的事呢?比如可以上傳圖片,對圖片做各種處理,然后保存等。