HTML5 File API — 讓前端操作文件變的可能(轉)

前言


在 HTML5 File API 出現(xiàn)之前,前端對于文件的操作是非常有局限性的,大多需要配合后端實現(xiàn)。出于安全角度考慮,從本地上傳文件時,代碼不可能獲取文件在用戶本地的地址,所以純前端不可能完成一些類似圖片預覽的功能。但是 File API 的出現(xiàn),讓這一切變成了可能。
跟著樓主由淺入深,了解下強大的 File API 吧。

FileList

FileList 對象針對表單的 file 控件。當用戶通過 file 控件選取文件后,這個控件的 files 屬性值就是 FileList 對象。它在結構上類似于數(shù)組,包含用戶選取的多個文件。如果 file 控件沒有設置 multiple 屬性,那么用戶只能選擇一個文件,F(xiàn)ileList 對象也就只有一個元素了。

<input type='file' multiple />
<script>
    document.querySelector('input').onchange = function() {
      console.log(this.files);
    };
</script>

比如我選擇了兩個文件,控制臺打?。?/p>

FileList {0: File, 1: File, length: 2}
0: File
1: File
  length:2
__proto__: Object

除了用 file 控件,采用拖放方式,也可以得到 FileList 對象。關于拖放,可以參考下我以前寫的文章 HTML5 — 讓拖放變的流行起來。

<textarea></textarea>
<script>
  var ipt = document.querySelector('textarea');
  ipt.ondragover = function () { return false; };

  // Add drop handler
  ipt.ondrop = function(e) {
    e.stopPropagation();
    e.preventDefault();
    e = e || window.event;
    var files = e.dataTransfer.files;

    console.log(files);
  };
</script>

選中兩個文件拖放到文本框中,打印結果和上面一致。

一般來說,我們不可能手動構造 FileList 對象,只能被動地讀取,也就是說只有用戶主動觸發(fā)了文件讀取行為,js 才能訪問到 FileList,而這通常發(fā)生在表單選擇文件或者拖拽文件中。

File


我們看到一個 FileList 對象包含了我們選中的 File 對象,那么一個 File 又有哪些屬性呢?我們可以打印出來看看。


image.png
  • name:文件名,該屬性只讀。
  • size:文件大小,單位為字節(jié),該屬性只讀。
  • type:文件的 MIME 類型,如果分辨不出類型,則為空字符串,該屬性只讀。
  • lastModified:文件的上次修改時間,格式為時間戳。
  • lastModifiedDate:文件的上次修改時間,格式為 Date 對象實例。

Blob


上圖中我們看到,F(xiàn)ile 對象是繼承自 Blob 對象的,Blob 又是什么鬼?

Blob(Binary Large Object)對象代表了一段二進制數(shù)據(jù),提供了一系列操作接口。其他操作二進制數(shù)據(jù)的 API(比如 File 對象),都是建立在 Blob 對象基礎上的,繼承了它的屬性和方法。

生成 Blob 對象有兩種方法:一種是使用 Blob 構造函數(shù),另一種是對現(xiàn)有的 Blob 對象使用 slice 方法切出一部分。

(1)Blob 構造函數(shù),接受兩個參數(shù)。第一個參數(shù)是一個包含實際數(shù)據(jù)的數(shù)組,第二個參數(shù)是數(shù)據(jù)的類型,這兩個參數(shù)都不是必需的。

var a = ["hello", "world"];
var myBlob = new Blob(a, { "type" : "text/xml" });
console.log(myBlob);

(2)Blob 對象的 slice 方法,將二進制數(shù)據(jù)按照字節(jié)分塊,返回一個新的 Blob 對象。

var a = ["hello", "world"];
var myBlob = new Blob(a, { "type" : "text/xml" });
var newBlob = myBlob.slice(0, 5);
console.log(newBlob);

Blob 對象有兩個只讀屬性:

  • size:二進制數(shù)據(jù)的大小,單位為字節(jié)。(文件上傳時可以在前端判斷文件大小是否合適)
  • type:二進制數(shù)據(jù)的 MIME 類型,全部為小寫,如果類型未知,則該值為空字符串。(文件上傳時可以在前端判斷文件類型是否合適)

FileReader


重頭戲來了,F(xiàn)ileReader API 才是我們接下去完成一些任務的關鍵。FileReader API 用于讀取文件,即把文件內容讀入內存。它的參數(shù)是 File 對象或 Blob 對象。

對于不同類型的文件,F(xiàn)ileReader 提供不同的方法讀取文件。

  • readAsBinaryString(Blob|File):返回二進制字符串,該字符串每個字節(jié)包含一個 0 到 255 之間的整數(shù)。(已廢棄)
  • readAsText(Blob|File, opt_encoding):返回文本字符串。默認情況下,文本編碼格式是 UTF-8,可以通過可選的格式參數(shù),指定其他編碼格式的文本。
  • readAsDataURL(Blob|File):返回一個基于 Base64 編碼的 data-uri 對象。
  • readAsArrayBuffer(Blob|File):返回一個 ArrayBuffer 對象。
    除了以上四種不同的讀取文件方法,F(xiàn)ileReader API 還有一個 abort 方法,用于中止文件上傳。
var reader = new FileReader();
reader.abort();

FileReader 對象采用異步方式讀取文件,可以為一系列事件指定回調函數(shù)。

  • onabort 方法:讀取中斷或調用 reader.abort() 方法時觸發(fā)。
  • onerror 方法:讀取出錯時觸發(fā)。
  • onload 方法:讀取成功后觸發(fā)。
  • onloadend 方法:讀取完成后觸發(fā),不管是否成功。觸發(fā)順序排在 onload 或 onerror 后面。
  • onloadstart 方法:讀取將要開始時觸發(fā)。
  • onprogress 方法:讀取過程中周期性觸發(fā)。(可以用來獲取文件讀取的進度)

以前在學習圖片的 base64 編碼的時候,寫了一篇文章 獲取圖片 base64 編碼的幾種方法,當時還沒有學習 File API,了解到 File API 也能做類似的事情,現(xiàn)在學到了,寫了個簡單的 demo http://hanzichi.github.io/2016/image2base64/,不僅能獲取圖片的 base64 編碼,同時也能獲取文字的 base64 編碼,代碼比較簡單就不放了,可以 猛戳這里。

獲取到了文件的 base64 編碼,做一些諸如圖片預覽的功能,也就手到擒來了,有興趣的可以自己嘗試下,類似的還有文字預覽啊,等等。

URL


你以為 File API 就這樣了嗎?非也,還有個強大的東西沒有介紹,URL 對象!

調用 URL 對象的 createObjectURL 方法,傳入一個 File 對象或者 Blob 對象,能生成一個鏈接,聽起來好像很吊的樣子。

var objecturl =  window.URL.createObjectURL(blob);

上面的代碼會對二進制數(shù)據(jù)生成一個 URL,這個 URL 可以放置于任何通??梢苑胖?URL 的地方,比如 img 標簽的 src 屬性。需要注意的是,即使是同樣的二進制數(shù)據(jù),每調用一次 URL.createObjectURL 方法,就會得到一個不一樣的 URL。

這個 URL 的存在時間,等同于網頁的存在時間,一旦網頁刷新或卸載,這個 URL 就失效。(File 和 Blob 又何嘗不是這樣呢)除此之外,也可以手動調用 URL.revokeObjectURL 方法,使 URL 失效。

window.URL.revokeObjectURL(objectURL);

舉個簡單的例子。

var blob = new Blob(["Hello hanzichi"]);
var a = document.createElement("a");
a.href = window.URL.createObjectURL(blob);
a.download = "a.txt";
a.textContent = "Download";

document.body.appendChild(a);

頁面上生成了一個超鏈接,點擊它就能下載一個名為 a.txt 的文件,里面的內容是 Hello hanzichi。

這里插點題外話,簡單介紹下 H5 新增的 download 屬性。對于一些諸如 exe,rar 等瀏覽器不能直接打開的文件類型,我們一般可以直接用一個 a 標簽,將其指向文件在服務端的地址,點擊即可下載。但是如果是一些瀏覽器能直接打開的文件,比如 txt,js 等,如果這樣設置一個超鏈接,點擊會直接打開文件,一般我們可以配合后端實現(xiàn),比如用 PHP。

$file_name = "1.txt"; // 下載文件名
$file_dir = dirname(__FILE__). '/'; //下載文件存放目錄
//輸入文件標簽
Header("Content-type: text/plain");
Header("Content-Disposition: attachment; filename=" . $file_name );

以上代碼需要文件的 Content-type 屬性值,安利一個網址,http://tool.oschina.net/commons ,各種文件類型的 Content-type 屬性值一網打盡!

如果考慮到安全性,header + fread 可能會顯得更嚴謹。

$file_name = "1.txt"; // 下載文件名
$file_dir = dirname(__FILE__). '/'; //下載文件存放目錄

Header("Content-type: text/plain");
Header("Content-Disposition: attachment; filename=" . $file_name );
echo fread($file, filesize($file_dir . $file_name));

但是現(xiàn)在我們只需要在 a 標簽上加上 download !

<a href="1.txt" download>download txt></a>

還可以給 download 加上屬性值,即為下載的文件名。

<a href="1.txt" download="2.txt">download txt></a>

可以省略.txt的后綴名,瀏覽器會自行判斷。
我們再回到 URL 上來。對于 File 或者 Blob 對象,我們可以這樣理解,它們的存在,依賴于頁面,而 URL 能給這些 "轉瞬即逝" 的二進制對象一個臨時的指向地址。

這個臨時的地址還有什么用呢?也能做圖片預覽,相比前面用 readAsDataURL 的實現(xiàn),更簡單了。

<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>

比如還有這樣的需求,前端上傳文件,要動態(tài)生成該文件的下載鏈接,也能用 URL 完成。

Canvas & dataURL & Blob


canvas 中有 toDataURL 函數(shù),可以將 canvas 轉為 dataURL 形式的 base64 編碼,而 Blob 也可以轉為 dataURL,這三者之間是否可以互相轉換?有沒有什么實用之處?

(1)canvas -> dataURL

用 toDataURL 方法,比較簡單,不多說。

(2)blob -> dataURL

用 FileReader 的 readAsDataURL 方法,使用方式可以看 這個 demo

(3)dataURL -> blob

這個函數(shù)有點屌

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 轉為 blob 也可以用 dataURL 做跳板,先將 canvas 轉為 dataURL(1),再用 dataURL 轉為 blob(3)。

利用它們之間的轉換可以做些什么好玩的事呢?比如可以上傳圖片,對圖片做各種處理,然后保存,看起來好像挺好玩的,等有空了搞個 demo 出來。

2016.11.11 add: canvas 有原生的 toBlob 方法,使得圖片文件可以被緩存或保存到本地。

Read more


File API 還能實現(xiàn)很多炫酷的功能,比如查看文件讀取的進度(onprogress ),分割文件,分段讀取文件,還能配合 ajax 使用,有興趣的可以參考以下文獻自行研究。

轉自https://www.cnblogs.com/zichi/p/html5-file-api.html#commentform

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 過來青春期之后的“騷年們”罪犯到的不再是青春到了,終于擺脫了痘痘的困擾的了!但是新的問題也來了,坑還在怎么辦?試過...
    Xio曦閱讀 215評論 0 3
  • 眉嫵 悵紅塵風唳,過旅難眠,極目曉聲遠。 已見清枝瘦,偏難住,無情初萼新減。 凍石筆染, 寫此間、云淡月淡。 豈堪...
    趙本塵閱讀 405評論 3 4
  • 昨晚,查看兒子的作業(yè),發(fā)現(xiàn)本要求默寫的古詩三首,居然用抄寫敷衍應對,再者做好的英語試卷一問三不知,頓時,我的...
    元素_d0b1閱讀 252評論 0 0
  • 我賦嚴冬一首詩, 梅花煮酒正當時。 諸友圍爐談舊事, 孩童研墨繪新枝。 繼往追思頻顧盼, 開來尤應寄相思。 一夢初...
    老夏_d855閱讀 181評論 0 0
  • 大年三十的傍晚,喧囂的城市終于像一個已經到站的公共汽車,停止了發(fā)動機的轟鳴聲。忙碌了一年的人們,或者和家人享受著闔...
    李禎祥閱讀 824評論 0 3

友情鏈接更多精彩內容