文章來源:小青年原創
發布時間:2016-08-16
關鍵詞:blob,File,FileReader,DataURI,URL
轉載需標注本文原始地址: http://zhaomenghuan.github.io...
寫在前面
這段時間一直有朋友在問文件上傳下載的事,搜一下論壇發現相關的問題不少,但是不夠系統,本着為人民服務的態度本文試着將一些問題整理一下,爭取用初學者可以更明確的去處理相關的問題。文件上傳是開發中繞不過的一個坎兒,對於很多沒有經驗的人來說,簡直懵逼,目前我所知道的上傳方式有下面這幾種:
- 傳統flash上傳
- 隱藏iframe框上傳
- 表單數據提交
- HTML5的新工具——File API
本文限於篇幅先介紹最后一種使用html5 File API進行文件上傳的相關細節。
歷史上,JavaScript無法處理二進制數據。如果一定要處理的話,只能使用charCodeAt()方法,一個個字節地從文字編碼轉成二進制數據,還有一種辦法是將二進制數據轉成Base64編碼,再進行處理。這兩種方法不僅速度慢,而且容易出錯。ECMAScript 5引入了Blob對象,允許直接操作二進制數據。Blob對象是一個代表二進制數據的基本對象,在它的基礎上,又衍生出一系列相關的API,用來操作文件。
File API
File 接口提供了文件的信息,以及文件內容的存取方法。
File對象可以用來獲取某個文件的信息,還可以用來讀取這個文件的內容。通常情況下,File對象是來自用戶在一個<input>元素上選擇文件后返回的FileList對象,也可以是來自由拖放操作生成的 DataTransfer對象.
通過input file標簽選擇文件
默認的input file標簽比較難看,需要自己改造,一般無非是將input file設置寬高,然后使用overflow: hidden;將多余的部分隱藏,在上面再蓋一個美化的按鈕或者提示語,如下圖:
瀏覽器原生的效果:

經過美化的效果:
代碼如下:
<!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對象的屬性:

- 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實現。
通過拖放操作選擇文件
預覽效果:

代碼實現:
<!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);
效果如下:

用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在瀏覽器中創建下載文件
寫文章不容易,也許寫這些代碼就幾分鍾的事,寫一篇大家好接受的文章或許需要幾天的醞釀,然后加上幾天的碼字,累並快樂着。如果文章對您有幫助請我喝杯咖啡吧!


