JavaScript進階學習(三)—— 基於html5 File API的文件操做

文章來源:小青年原創
發佈時間:2016-08-16
關鍵詞:blob,File,FileReader,DataURI,URL
轉載需標註本文原始地址: http://zhaomenghuan.github.io...javascript

寫在前面

這段時間一直有朋友在問文件上傳下載的事,搜一下論壇發現相關的問題很多,可是不夠系統,本着爲人民服務的態度本文試着將一些問題整理一下,爭取用初學者能夠更明確的去處理相關的問題。文件上傳是開發中繞不過的一個坎兒,對於不少沒有經驗的人來講,簡直懵逼,目前我所知道的上傳方式有下面這幾種:css

  • 傳統flash上傳
  • 隱藏iframe框上傳
  • 表單數據提交
  • HTML5的新工具——File API

本文限於篇幅先介紹最後一種使用html5 File API進行文件上傳的相關細節。html

歷史上,JavaScript沒法處理二進制數據。若是必定要處理的話,只能使用charCodeAt()方法,一個個字節地從文字編碼轉成二進制數據,還有一種辦法是將二進制數據轉成Base64編碼,再進行處理。這兩種方法不只速度慢,並且容易出錯。ECMAScript 5引入了Blob對象,容許直接操做二進制數據。Blob對象是一個表明二進制數據的基本對象,在它的基礎上,又衍生出一系列相關的API,用來操做文件。前端

File API

File 接口提供了文件的信息,以及文件內容的存取方法。html5

File對象能夠用來獲取某個文件的信息,還能夠用來讀取這個文件的內容。一般狀況下,File對象是來自用戶在一個<input>元素上選擇文件後返回的FileList對象,也能夠是來自由拖放操做生成的 DataTransfer對象.java

經過input file標籤選擇文件

默認的input file標籤比較難看,須要本身改造,通常無非是將input file設置寬高,而後使用overflow: hidden;將多餘的部分隱藏,在上面再蓋一個美化的按鈕或者提示語,以下圖:git

瀏覽器原生的效果:github

clipboard.png

通過美化的效果:
clipboard.pngweb

代碼以下:chrome

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            *{
                margin: 0px;
                padding: 0px;
            }
            .filePicker{
                width: 160px;
                height: 44px;
                line-height: 44px;
                text-align: center;
                color: #fff;
                background: #00b7ee;        
            }
            .filePicker input[type="file"] {
                position: relative;
                top: -44px;
                left: 0px;
                width: 160px;
                height: 44px;
                opacity: 0;
                cursor: pointer;
                overflow: hidden;
                z-index: 0;
            }
            
            .container{
                width: 160px;
                margin: 30px auto;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <input type="file" name="" id="" value="" />
        </div>    
        <div class="container">
            <div class="filePicker">
                <label>點擊選擇文件</label>
                <input id="fileInput" type="file" name="file" multiple="multiple" accept="image/*">
            </div>
        </div>
    </body>
</html>

咱們能夠經過給input file標籤設置accept屬性進行文件選擇過濾,該屬性的值必須爲一個逗號分割的列表,包含了多個惟一的內容類型聲明:

  • 以 STOP 字符 (U+002E) 開始的文件擴展名。(例如:".jpg,.png,.doc")
  • 一個有效的 MIME 類型,但沒有擴展名
  • audio/* 表示音頻文件 HTML5
  • video/* 表示視頻文件 HTML5
  • image/* 表示圖片文件

設置multiple屬性能夠進行設置爲多選。

設置capture屬性能夠進行設置打開攝像拍照或者錄像。

Capture Image: 
<input type="file" accept="image/*" capture="camera"> 
Capture Audio: 
<input type="file" accept="audio/*" capture="microphone"> 
Capture Video: 
<input type="file" accept="video/*" capture="camcorder">

multiple屬性和capture屬性不能同時生效。

經過File API,咱們能夠在用戶選取一個或者多個文件以後,訪問到表明了所選文件的一個或多個File對象,這些對象被包含在一個FileList對象中。全部type屬性(attribute)爲file的<input>元素都有一個files屬性,用來存儲用戶所選擇的文件。files有一個length屬性和item方法,咱們能夠經過files[index]或者files.item(index)獲取咱們選擇的file對象。能夠經過change事件監聽input file輸入完成事件:

var fileInput = document.getElementById("fileInput");
fileInput.addEventListener('change', function(event) {
    var file = fileInput.files[0];
    // 或file = fileInput.files.item(0);
    console.log(file);
}, false);

File API提供File對象,它是FileList對象的成員,包含了文件的一些元信息,好比文件名、上次改動時間、文件大小和文件類型。下圖能夠File對象的屬性:

clipboard.png

  • lastModifiedDate:文件對象最後修改的日期
  • name:文件名,只讀字符串,不包含任何路徑信息.
  • size:文件大小,單位爲字節,只讀的64位整數.
  • type:MIME類型,只讀字符串,若是類型未知,則返回空字符串.

例如:咱們能夠根據size換算出咱們習慣的文件大小表達方式:

/**
 * 讀文件大小
 * @param {Object} file
 */
function readFileSize(file){
    var size = file.size / 1024;
    var aMultiples = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
    
    var fileSizeString = '';
    for(var i = 0; size > 1; size /= 1024, i++) {
       fileSizeString = size.toFixed(2) + " " + aMultiples[i];
    }
    return fileSizeString;
}

有時候咱們但願限制用戶上傳的文件大小,能夠經過這個方法先作判斷。同時咱們能夠經過type屬性判斷用戶的文件類型,可是這種方法不可靠,由於用戶能夠經過改變後綴名實現。

不少新手企圖經過input file標籤得到文件完整路徑,因爲瀏覽器安全機制,這個是不被容許的,可是有時候咱們但願選擇完圖片預覽一下圖片,這個時候咱們就能夠用FileReader API實現。

經過拖放操做選擇文件

預覽效果

clipboard.png

代碼實現

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            .dropbox{
                width: 300px;
                height: 300px;
                margin: 20px;
                border: 3px dashed #e6e6e6;
            }
            .area{
                margin: 100px auto;
                width: 100px;
                height: 100px;
                background-repeat: no-repeat;
                background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAABLCAIAAAB7tddWAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1Q0VBNzA0MjEyMDUxMUUzODk2Q0JFM0Q1RjE4QkExQyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1Q0VBNzA0MzEyMDUxMUUzODk2Q0JFM0Q1RjE4QkExQyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjAzNDA2MkY1MTIwMzExRTM4OTZDQkUzRDVGMThCQTFDIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAzNDA2MkY2MTIwMzExRTM4OTZDQkUzRDVGMThCQTFDIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+K6izdgAAAvpJREFUeNrsnFmPqkAQhWmX667gEp9c/v+/MkSDG+4LrvdcSYgRbw/0ALZQ9WBUJOn+uqvqHGCG3e93hUJRUoSAQBAIAkEgCASBIBAE4neRicEcII51Xb/dbnjPGOt0OqlUKok7ApN3jIKwY6DUIBAEgkAQCALho/X47TeXy8U0TcuyrtdrZKPs9/v2m8FgINYgf9QX/gTV+Xw2DCNKBJKmxmKxsAVc0kEcDgfyGq8CNp/Pa5qWy+WiHG6v13v7/XPt6Ha7Al5D3HQ1Go1sNkvtU8lkMsmtESSoCASBSFqEW/DQ0tbr9W63O51OKK6FQkFV1XQ6nSwQ0OOTyQSvjknZbDaA0mq1QCQafSFFasxmM4eCE1Do0+lUQrcSFggsPhzq20NgsVqtkpIax+MxMs+C/aXruvMxaonND75J9W5hUWWxuYAVdRfTg8EplUphGJywQPAFuBd5Dlhw/aDwwgVtCDgCdzph1QisG+dosVjkn44WYxjGC4XnvBuNRtvt9gtA2Hv47SGsZKVS4ef8eDzG4vMVCrpSgEU3xPbZbDar1erLl1AQ7XabU8xAAXvB3XffBnIHwkR2QcUYq9fr5XIZOxkTg6BEkeNLKdQF7AWPFBy1AoUmu8RG/HmE91nxM+J/ORIr07VcLvf7feCt+stAQGIBRNJtOJolDAhdj/hXGj5+u+TzIKAF+MbkK00XFta2BhDRUE0/9gv8Elogbu4TBW8+nyPhHSeuaVqtVuNQC6TzyQUCXsg0TbfyAxfIKogr9ynP1GJyYQZ57qbg7AuIRfclKZwSlDqWBQSmxM9zFALYh+fFBwJJSkNgqeFxSrAPw+EQ9QJew7Is2Sj8FgSW2nu1gylYPkKRMsRTA+4IcjA2fxsnDkLOq/IfACFP54uP1yAQBIJAEIh4gWCPkHk+GJ7AjU/fICJ+qlIghEfoDwQMtRjvyLYDRih4rsDT+bBM9tP5kuhrzN++e6SqqvCdYUb/SIO6BoEgEASCQBAIAkEgCEQg8VeAAQAB1bbO2qoeewAAAABJRU5ErkJggg==");
            }
            
            #preview img{
                width: 100px;
                height: 100px;
            }
        </style>
    </head>
    <body>
        <div id="dropbox" class="dropbox">
            <div class="area"></div>
        </div>
        <div id="preview"></div>
        
        <script type="text/javascript">
            var dropbox = document.getElementById("dropbox");
            var preview = document.getElementById("preview");
            
            dropbox.addEventListener("dragenter", function(e){
                e.stopPropagation();
                  e.preventDefault();
            }, false);
            
            dropbox.addEventListener("dragover", function(e){
                e.stopPropagation();
                  e.preventDefault();
            }, false);
            
            dropbox.addEventListener("drop", function(e){
                e.stopPropagation();
                  e.preventDefault();
            
                  var dt = e.dataTransfer;
                  var files = dt.files;
                  
                  for (var i = 0; i < files.length; i++) {
                    var file = files[i];
                    var imageType = /^image\//;

                    if ( !imageType.test(file.type) ) {
                          continue;
                    }
                    
                    // 填充選擇的圖片到展現區
                    var img = document.createElement("img");
                    img.classList.add("obj");
                    img.file = file;
                    preview.appendChild(img);
                    
                    // 讀取File對象中的內容
                    var reader = new FileReader();
                    reader.onload = (function(aImg) { 
                      return function(e) { 
                        aImg.src = e.target.result; 
                      }; 
                    })(img);
                    reader.readAsDataURL(file);
                }
            }, false);
        </script>
    </body>
</html>

在這個例子中,ID 爲 dropbox 的元素所在的區域是咱們的拖放目的區域。咱們須要在該元素上綁定 dragenter,dragover,和drop 事件。咱們必須阻止dragenter和dragover事件的默認行爲,這樣才能觸發 drop 事件。咱們從drop 事件對象中獲取到dataTransfer對象,這個對象包含Filelist對象。

FileReader API

使用FileReader對象,web應用程序能夠異步的讀取存儲在用戶計算機上的文件(或者原始數據緩衝)內容,可使用File對象或者Blob對象來指定所要處理的文件或數據.其中File對象能夠是來自用戶在一個<input>元素上選擇文件後返回的FileList對象,也能夠來自拖放操做生成的 DataTransfer對象,還能夠是來自在一個HTMLCanvasElement上執行mozGetAsFile()方法後的返回結果。

DataURI對象

在上面經過拖放操做選擇文件的例子中,咱們使用了"data:image/png;base64,xxxxxxxxxxxxx"這種形式的字符串做爲背景,而不是圖片,選擇的圖片展現也是使用這種形式。這種字符串叫作DataURI對象,容許將一個小文件進行編碼後嵌入到另一個文檔裏,格式爲:

data:[<MIME type>][;charset=<charset>][;base64],<encoded data>

總體能夠視爲三部分,即聲明:參數+數據,逗號左邊的是各類參數,右邊的是數據。

URL是uniform resource locator的縮寫,在web中的每個可訪問資源都有一個URL地址,例如圖片,HTML文件,js文件以及style sheet文件,咱們能夠經過這個地址去download這個資源。其實URL是URI的子集,URI是uniform resource identifier的縮寫。URI是用於獲取資源,包括其附加的信息的一種協議。附加信息多是地址,也可能不是地址,若是是地址,那麼這時URI就變成URL了。注意的是data URI不是URL,由於它並不包含資源的公共地址。

咱們能夠經過FileReader 的readAsDataURL方法得到:

var reader = new FileReader();
reader.onload = function() {
    console.log(this.result);
}
reader.readAsDataURL(file);

有時候咱們須要將DataURI對象轉blob對象:

/**
 * dataURI 轉 blob
 * @param {Object} dataURI
 */
function dataURItoBlob(dataURI) {
    var arr = dataURI.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});
}

URL對象

咱們除了可使用base64字符串做爲內容的DataURI將一個文件嵌入到另一個文檔裏,還可使用URL對象。URL對象用於生成指向File對象或Blob對象的URL。

window.URL.createObjectURL

靜態方法會建立一個 DOMString,它的 URL 表示參數中的對象。這個 URL 的生命週期和建立它的窗口中的 document 綁定。這個新的URL 對象表示着指定的 File 對象或者 Blob 對象。

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

window.URL.revokeObjectURL

靜態方法用來釋放一個以前經過調用 window.URL.createObjectURL() 建立的已經存在的 URL 對象。當你結束使用某個 URL 對象時,應該經過調用這個方法來讓瀏覽器知道再也不須要保持這個文件的引用了。

window.URL.revokeObjectURL(objecturl)

例如:使用對象URL來顯示圖片:

window.URL = window.URL || window.webkitURL;

var img = document.createElement("img");
img.src = window.URL.createObjectURL(blob);
img.height = 60;
img.onload = function(e) {
    window.URL.revokeObjectURL(this.src);
}
document.body.appendChild(img);

FileReader API詳解

狀態常量

  • EMPTY:值爲0,尚未加載任何數據;
  • LOADING:值爲1,數據正在被加載;
  • DONE:值爲2,已完成所有的讀取請求。

屬性

  • error:在讀取文件時發生的錯誤, 只讀;
  • readyState:代表FileReader對象的當前狀態,值爲State constants中的一個,只讀;
  • result:取到的文件內容,這個屬性只在讀取操做完成以後纔有效,而且數據的格式取決於讀取操做是由哪一個方法發起的,只讀。

方法

  • abort():停止該讀取操做.在返回時,readyState屬性的值爲DONE.
  • readAsArrayBuffer():開始讀取指定的Blob對象或File對象中的內容. 當讀取操做完成時,readyState屬性的值會成爲DONE,若是設置了onloadend事件處理程序,則調用之.同時,result屬性中將包含一個ArrayBuffer對象以表示所讀取文件的內容.
  • readAsBinaryString():開始讀取指定的Blob對象或File對象中的內容. 當讀取操做完成時,readyState屬性的值會成爲DONE,若是設置了onloadend事件處理程序,則調用之.同時,result屬性中將包含所讀取文件的原始二進制數據.
  • readAsDataURL():開始讀取指定的Blob對象或File對象中的內容. 當讀取操做完成時,readyState屬性的值會成爲DONE,若是設置了onloadend事件處理程序,則調用之.同時,result屬性中將包含一個data: URL格式的字符串以表示所讀取文件的內容.
  • readAsText():開始讀取指定的Blob對象或File對象中的內容. 當讀取操做完成時,readyState屬性的值會成爲DONE,若是設置了onloadend事件處理程序,則調用之.同時,result屬性中將包含一個字符串以表示所讀取的文件內容.

事件處理

  • onabort:當讀取操做被停止時調用.
  • onerror:當讀取操做發生錯誤時調用.
  • onload:當讀取操做成功完成時調用.
  • onloadend:當讀取操做完成時調用,不論是成功仍是失敗.該處理程序在onload或者onerror以後調用.
  • onloadstart:當讀取操做將要開始以前調用.
  • onprogress:在讀取數據過程當中週期性調用.

上傳實例:以二進制流上傳文件

var fileInput = document.getElementById("fileInput");
fileInput.addEventListener('change', function(event) {
    var file = fileInput.files[0];
    if (file) {
        var reader = new FileReader();  
        var xhr = new XMLHttpRequest();
        xhr.onprogress=function(e){
            var percentage = Math.round((e.loaded * 100) / e.total);
            console.log("percentage:"+percentage);
        }
        xhr.onload=function(e){
            console.log("percentage:100");
        }
        xhr.open("POST", "這裏填寫服務器地址");  
        reader.onload = function(evt) {
            xhr.send(evt.target.result);
        };
        reader.readAsBinaryString(file);
    }
});

blob 二進制大對象

BLOB (binary large object),二進制大對象,是一個能夠存儲二進制文件的容器。

建立Blob對象的方法有幾種,能夠調用Blob構造函數,還可使用一個已有Blob對象上的slice()方法切出另外一個Blob對象,還能夠調用canvas對象上的toBlob方法。

第一次見到這個詞是半年以前,那個時候竟然沒有聽過blob,而後上網查了一下,巴拉巴拉一大堆,當時是不理解的。

看了前面的一系列API和對象,或許不少同窗開始暈了,可是在一開始說到的blob對象,咱們一直沒有提到,若是本文不說起,顯然是不合理的。畢竟做爲File對象的爸爸,blob勞苦功高。上述的FileReader對象也能夠操做blob對象。

Blob對象有兩個只讀屬性:

  • size:二進制數據的大小,單位爲字節。
  • type:二進制數據的MIME類型,所有爲小寫,若是類型未知,則該值爲空字符串。
    在Ajax操做中,若是xhr.responseType設爲blob,接收的就是二進制數據。

Blob 構造函數生成blob對象

Blob構造函數,接受兩個參數。第一個參數是一個包含實際數據的數組,第二個參數是數據的類型,這兩個參數都不是必需的。數組元素能夠是任意多個的ArrayBuffer,ArrayBufferView (typed array), Blob,或者 DOMString對象。
例如:

var arr = ['<h1>hello world</h1>'];
var blob = new Blob(arr, { "type" : "text/xml" }); // the blob
console.log(blob);

效果以下:

clipboard.png

用JS在瀏覽器中建立下載文件

前端不少項目中,都有文件下載的需求,特別是JS生成文件內容,而後讓瀏覽器執行下載操做(例如在線圖片編輯、在線代碼編輯、iPresst等)但受限於瀏覽器,不少狀況下咱們都只能給出個連接,讓用戶點擊打開-》另存爲。以下面這個連接:

<a href="file.js">file.js</a>

用戶點擊這個連接的時候,瀏覽器會打開並顯示連接指向的文件內容,顯然,這並無實現咱們的需求。HTML5中給a標籤增長了一個download屬性,只要有這個屬性,點擊這個連接時瀏覽器就不在打開連接指向的文件,而是改成下載(目前只有chrome、firefox和opera支持)。下載時會直接使用連接的名字來做爲文件名,可是是能夠改的,只要給download加上想要的文件名便可,如:download="not-a-file.js"。可是這樣還不夠,以上的方法只適合用在文件是在服務器上的狀況。若是在瀏覽器端js生成的內容,想讓瀏覽器進行下載要如何辦到呢?DataURI能夠實現這個效果,可是DataURI的文件類型被限制了,咱們這裏能夠變通一下實現blob對象。

<a id="aLink">下載</a>
<script type="text/javascript">
    function downloadFile (el, fileName, content) {
        var aLink = document.querySelector(el);
        var blob = new Blob([content]);                
        aLink.download = fileName;
        aLink.href = URL.createObjectURL(blob);
    }
    document.querySelector('#aLink').addEventListener('click',function () {
        downloadFile('#aLink', 'hello.txt', '<h1>hello world</h1>');
    })
</script>

Blob對象的slice方法生成blob對象

Blob對象的slice方法,將二進制數據按照字節分塊,返回一個新的Blob對象。

var newBlob = oldBlob.slice(startingByte, endindByte);

下面是一個使用XMLHttpRequest對象,將大文件分割上傳的例子。

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };
  xhr.send(blobOrFile);
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  var blob = this.files[0];

  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
  const SIZE = blob.size;

  var start = 0;
  var end = BYTES_PER_CHUNK;

  while(start < SIZE) {
    upload(blob.slice(start, end));

    start = end;
    end = start + BYTES_PER_CHUNK;
  }
}, false);

參考文檔

在web應用中使用文件
DataURI詳解
文件和二進制數據的操做
理解DOMString、Document、FormData、Blob、File、ArrayBuffer數據類型
用JS在瀏覽器中建立下載文件

寫文章不容易,也許寫這些代碼就幾分鐘的事,寫一篇你們好接受的文章或許須要幾天的醞釀,而後加上幾天的碼字,累並快樂着。若是文章對您有幫助請我喝杯咖啡吧!

clipboard.png

相關文章
相關標籤/搜索