天天看點

HTML5 File API

HTML5 引入了一個 File API 用以提供使用者上傳檔案的資訊,并允許網頁中的 JavaScript 通路其内容。

以下是一些表單 file 控件:

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">
<input type="file" accept="image/*;capture=camera">直接調用相機(測試安卓可以,iphone還是有相冊)
<input type="file" accept="image/*" />調用相機 圖檔或者相冊
<input type="file" multiple accept="image/*" />調用相冊      

1 FileList 對象

FileList 對象針對表單的 file 控件。 當使用者通過 file 控件選取檔案後,這個控件的 files 屬性值就是 FileList 對象。

// 多選控件

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

};
</script>      

控制台輸出:

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

除了用 file 控件,采用拖放方式,也可以得到 FileList 對象。

<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 對象,隻能被動地讀取,也就是說隻有使用者主動觸發了檔案讀取行為,js 才能通路到 FileList,而這通常發生在表單選擇檔案或者拖拽檔案中。

2 File 對象

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

HTML5 File API
  • 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 構造函數,接受兩個參數。第一個參數是一個包含實際資料的數組,第二個參數是資料的類型,這兩個參數都不是必需的。

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

(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 類型,全部為小寫,如果類型未知,則該值為空字元串。(檔案上傳時可以在前端判斷檔案類型是否合适)

4FileReader

重頭戲來了,FileReader API 才是我們接下去完成一些任務的關鍵。

FileReader API 用于讀取檔案,即把檔案内容讀入記憶體。它的參數是 File 對象或 Blob 對象。

對于不同類型的檔案,FileReader 提供不同的方法讀取檔案。

  • readAsBinaryString(Blob|File):傳回二進制字元串,該字元串每個位元組包含一個 0 到 255 之間的整數。(已廢棄)
  • 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 編碼的時候,寫了一篇文章 擷取圖檔 base64 編碼的幾種方法,

當時還沒有學習 File API,了解到 File API 也能做類似的事情,現在學到了,

寫了個簡單的 demo ​​http://hanzichi.github.io/2016/image2base64/​​,不僅能擷取圖檔的 base64 編碼,

同時也能擷取文字的 base64 編碼。

<title>擷取圖檔 base64 編碼</title>
<meta charset="utf-8">
<style>
  textarea {
    width: 100%;
    height: 200px;
    margin-top: 10px;
  }
</style>
<body>
<h1>上傳圖檔或者拖動圖檔到框内擷取圖檔 base64 編碼</h1>
<input type='file' multiple /><br/>
<textarea></textarea>
<script>
  document.querySelector('input').onchange = function() {
    var reader = new FileReader();
    reader.onload = function() {
      var dataURL = reader.result;
      document.querySelector('textarea').innerHTML = dataURL;
    };
    reader.readAsDataURL(this.files[0]);
  };
  // drag & drop
  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;
    var reader = new FileReader();
    reader.onload = function() {
      var dataURL = reader.result;
      document.querySelector('textarea').innerHTML = dataURL;
    };
    reader.readAsDataURL(files[0]);
  };
</script>
</body>      

擷取到了檔案的 base64 編碼,做一些諸如圖檔預覽的功能,也就手到擒來了,有興趣的可以自己嘗試下,

類似的還有文字預覽啊,等等。

5 URL

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

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

var      

上面的代碼會對二進制資料生成一個 URL,這個 URL 可以放置于任何通常可以放置 URL 的地方,比如 img 标簽的 src 屬性。需要注意的是,即使是同樣的二進制資料,每調用一次 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 屬性。

是否支援download屬性的監測

要監測目前浏覽器是否支援​​

​download​

​屬性,一行JS代碼就可以了,如下:

var isSupportDownload = 'download' in document.createElement('a');      

對于一些諸如 exe,rar 等浏覽器不能直接打開的檔案類型,我們一般可以直接用一個 a 标簽,将其指向檔案在服務端的位址,點選即可下載下傳。但是如果是一些浏覽器能直接打開的檔案,比如 txt,js 等,如果這樣設定一個超連結,點選會直接打開檔案,一般我們可以配合後端實作,比如用 PHP。

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

以上代碼需要檔案的 Content-type 屬性值,安利一個網址,各種檔案類型的 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));      

但是現在我們隻需要在 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 的實作,更簡單了。

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

比如還有這樣的需求,前端上傳檔案,要動态生成該檔案的下載下傳連結,也能用 URL 完成。

6 onprogress 事件

想要上傳圖檔的時候顯示進度條,就要使用上 HTML5 中的 onprogress 事件了。

onprogress 事件可以作用于 <video> 或 <audio> 标簽中,作為其狀态回調函數的作用。

也可以是作為 XMLHttpRequest 的指定事件,XMLHttpRequest 對象,傳送資料的時候,有一個 progress 事件,用來傳回進度資訊。

它分成 上傳和下載下傳 兩種情況:

下載下傳的 progress 事件屬于 XMLHttpRequest 對象

上傳的 progress 事件屬于 XMLHttpRequest.upload 對象

當上傳或下載下傳時,會頻繁調用該方法。該方法接受一個事件參數 event,其中 event.total 是需要傳輸的總位元組,event.loaded 是已經傳輸的位元組。如果event.lengthComputable 不為真,則 event.total 等于 0。

例子代碼:

function uploadFile1(file,desc,catid){
var $process = $("#process_"+file.size);
var formData = new FormData(); //以下為像背景送出圖檔資料
formData .append('img_url', file);
formData .append('img_desc',desc);
formData .append('cat_id',catid);
var xhr=new XMLHttpRequest();
xhr.open('post','shop_gallery.php?act=update',true);
xhr.onreadystatechange=function (){
if(this.readyState==4){
$process.css("width","0%");
if (!(img_url.length == 0)) {
img_desc.splice(0, 1);
uploadFile1(img_url[0],img_desc[0],catid);
}
}
}
xhr.upload.onprogress=function (ev){
if(ev.lengthComputable){
var precent=100 * ev.loaded/ev.total;
$process.css("width",precent+'%');
if (precent == 100){
delimg1($process);
if (img_url.length == 0){
setTimeout("refresh("+catid+")", 1000 );
}
}
}
}
// 
xhr.send(formData);
}      

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

利用它們之間的轉換可以做些什麼好玩的事呢?比如可以上傳圖檔,對圖檔做各種處理,然後儲存。

canvas 有原生的 ​​toBlob​​ 方法,使得圖檔檔案可以被緩存或儲存到本地。

Read more

File API 還能實作很多炫酷的功能,比如檢視檔案讀取的進度(onprogress ),分割檔案,分段讀取檔案,還能配合 ajax 使用,有興趣的可以參考以下文獻自行研究。

  • ​​檔案和二進制資料的操作​​(本文大量參考或者直接引用了阮老師的這篇文章内容,不得不說阮老師的 tutorial 寫的真的非常的好,老少皆宜)
  • ​​通過 File API 使用 JavaScript 讀取檔案​​
  • ​​Using files from web applications​​
  • ​​HTML5 讀取 本地檔案 File Api 使用​​
  • ​​FileAPI w3​​
  • ​​了解 HTML/HTML5 中的 download 屬性​​
  • ​​在浏覽器端用 JS 建立和下載下傳檔案​​
  • DataURL 與 File,Blob,canvas 對象之間的互相轉換的 Javascript