本文轉自:轉:http://hushicai.com/2014/03/29/html5-du-qu-ben-di-wen-jian.html感謝大神分享。
常見的語言比如php、shell等,是如何讀取文件的呢?
實際上,大多數語言都需要先獲取文件句柄,然后調用文件訪問接口,打開文件句柄,讀取文件!
那么,HTML5是否也是這樣的呢?
答案是肯定的!
HTML5為我們提供了一種與本地文件系統交互的標准方式:File Api。
該規范主要定義了以下數據結構:
File
FileList
Blob
HTML5訪問本地文件系統時,需要先獲取File
對象句柄,怎么獲取文件引用句柄呢?
選擇文件
首先檢測一下當前瀏覽器是否支持File Api
:
function isSupportFileApi() { if(window.File && window.FileList && window.FileReader && window.Blob) { return true; } return false; }
HTML5雖然可以讓我們訪問本地文件系統,但是js只能被動地讀取,也就是說只有用戶主動觸發了文件讀取行為,js才能訪問到File Api
,這通常發生在表單選擇文件或者拖拽文件。
表單輸入
表單提交文件是最常見的場景,用戶選擇文件后,觸發了文件選擇框的change事件,通過訪問文件選擇框元素的files
屬性可以拿到選定的文件列表。
如果文件選擇框指定了multiple,則一個文件選擇框可以同時選擇多個文件,files
包含了所有選擇的文件對象;如果沒有指定,則只能選擇一個文件,files[0]
就是所選擇的文件對象。
function fileSelect1(e) { var files = this.files; for(var i = 0, len = files.length; i < len; i++) { var f = files[i]; html.push( '<p>', f.name + '(' + (f.type || "n/a") + ')' + ' - ' + f.size + 'bytes', '</p>' ); } document.getElementById('list1').innerHTML = html.join(''); } document.getElementById('file1').onchange = fileSelect1;
拖拽
拖拽是另一種常見的文件訪問場景,這種方式通過一個叫dataTransfer
的接口來獲得拖拽的文件列表,更多關於dataTransfer。
拖拽同樣支持多選,用戶可以拖拽多個文件。
function dropHandler(e) { e.stopPropagation(); e.preventDefault(); var files = e.dataTransfer.files; for(var i = 0, len = files.length; i < len; i++) { var f = files[i]; html.push( '<p>', f.name + '(' + (f.type || "n/a") + ')' + ' - ' + f.size + 'bytes', '</p>' ); } document.getElementById('list2').innerHTML = html.join(''); } function dragOverHandler(e) { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dragEffect = 'copy'; } var drag = document.getElementById('drag'); drag.addEventListener('drop', dropHandler, false); drag.addEventListener('dragover', dragOverHandler, false);
PS:
拖拽有個特別需要注意的事情就是,阻止事件冒泡和事件默認行為,防止瀏覽器自動打開文件等行為,比如拖拽一個pdf,瀏覽器可能會打開pdf。
至此,我們知道,我們可以通過兩種方式來獲得文件句柄,那么如何讀取文件內容呢?
讀取文件
HTML5提供了一個叫FileReader
的接口,用於異步讀取文件內容,它主要定義了以下幾個方法:
readAsBinaryString(File|Blob)
readAsText(File|Blob [, encoding])
readAsDataURL(File|Blob)
readAsArrayBuffer(File|Blob)
FileReader
還提供以下事件:
- onloadstart
- onprogress
- onload
- onabort
- onerror
- onloadend
一旦調用了以上某個方法讀取文件后,我們可以監聽以上任何一個事件來獲得進度、結果等。
預覽本地圖片
這里主要用到FileReader的readAsDataURL
方法,通過將圖片數據讀取成Data URI的方法,將圖片展示出來。
function fileSelect2(e) { e = e || window.event; var files = this.files; var p = document.getElementById('preview2'); for(var i = 0, f; f = files[i]; i++) { var reader = new FileReader(); reader.onload = (function(file) { return function(e) { var span = document.createElement('span'); span.innerHTML = '<img style="padding: 0 10px;" width="100" src="'+ this.result +'" alt="'+ file.name +'" />'; p.insertBefore(span, null); }; })(f); //讀取文件內容 reader.readAsDataURL(f); } } document.getElementById('files2').addEventListener('change', fileSelect2, false);
調用FileReader的readAsDataURL接口時,瀏覽器將異步讀取文件內容,通過給FileReader實例監聽一個onload事件,數據加載完畢后,在onload事件處理中,通過reader的result屬性即可獲得文件內容。
預覽文本文件
這里主要用到FileReader的readAsText
,對於諸如mimeType為text/plain、text/html等文件均認為是文本文件,即mineType為text開頭都可以用這個方法來預覽。
function fileSelect3(e) { e = e || window.event; var files = this.files; var p = document.getElementById('preview3'); for(var i = 0, f; f = files[i]; i++) { var reader = new FileReader(); reader.onload = (function(file) { return function(e) { var div = document.createElement('div'); div.className = "text" div.innerHTML = encodeHTML(this.result); p.insertBefore(div, null); }; })(f); //讀取文件內容 reader.readAsText(f); } } document.getElementById('files3').addEventListener('change', fileSelect3, false);
PS:由於需要在頁面上預覽文本,所以則需要對文件中的html特殊字符進行實體編碼,避免瀏覽器解析文件中的html代碼。
監控讀取進度
既然FileReader是異步讀取文件內容,那么就應該可以監聽它的讀取進度。
事實上,FileReader的onloadstart以及onprogress等事件,可以用來監聽FileReader的讀取進度。
在onprogress的事件處理器中,有一個ProgressEvent對象,這個事件對象實際上繼承了Event對象,提供了三個只讀屬性:
lengthComputable
loaded
total
通過以上幾個屬性,即可實時顯示讀取進度,不過需要注意的是,此處的進度條是針對單次讀取的進度,即一次readAsBinaryString
等方法的讀取進度。
var input4 = document.getElementById('file4'); var bar = document.getElementById('progress-bar'); var progress = document.getElementById('progress'); function startHandler(e) { bar.style.display = 'block'; } function progressHandler(e) { var percentLoaded = Math.round((e.loaded / e.total) * 100); if (percentLoaded < 100) { progress.style.width = percentLoaded + '%'; progress.textContent = percentLoaded + '%'; } } function loadHandler(e) { progress.textContent = '100%'; progress.style.width = '100%'; } function fileSelect4(e) { var file = this.files[0]; if(!file) { alert('請選擇文件!'); return false; } if(file.size > 500 * 1024 * 1024) { alert('文件太大,請選擇500M以下文件,防止瀏覽器崩潰!'); return false; } progress.style.width = '0%'; progress.textContent = '0%'; var reader = new FileReader(); reader.onloadstart = startHandler; reader.onprogress = progressHandler; reader.onload = loadHandler; reader.readAsBinaryString(this.files[0]); } input4.onchange = fileSelect4;
分割文件
有的時候,一次性將一個大文件讀入內存,並不是一個很好的選擇(如果文件太大,可能直接導致瀏覽器崩潰),上述的監聽進度示例就有可能在文件太大的情況下崩潰。
更穩健的方法是分段讀取!
分段讀取文件
HTML5 File Api提供了一個slice
方法,允許分片讀取文件內容。
function readBlob(start, end) { var files = document.getElementById('file5').files; if(!files.length) { alert('請選擇文件'); return false; } var file = files[0], start = parseInt(start, 10) || 0, end = parseInt(end, 10) || (file.size - 1); var r = document.getElementById('range'), c = document.getElementById('content'); var reader = new FileReader(); reader.onloadend = function(e) { if(this.readyState == FileReader.DONE) { c.textContent = this.result; r.textContent = "Read bytes: " + (start + 1) + " - " + (end + 1) + " of " + file.size + " bytes"; } }; var blob; if(file.webkitSlice) { blob = file.webkitSlice(start, end + 1); } else if(file.mozSlice) { blob = file.mozSlice(start, end + 1); } else if(file.slice) { blob = file.slice(start, end + 1); } reader.readAsBinaryString(blob); }; document.getElementById('file5').onchange = function() { readBlob(10, 100); }
本例使用了FileReader的onloadend事件來檢測讀取成功與否,如果用onloadend則必須檢測一下FileReader的readyState,因為read abort時也會觸發onloadend事件,如果我們采用onload,則可以不用檢測readyState。
分段讀取進度
那分段讀取一個大文件時,如何監控整個文件的讀取進度呢?
這種情況下,因為我們調用了多次FileReader的文件讀取方法,跟上文一次性把一個文件讀到內存中的情況不大相同,不能用onprogress來監控。
我們可以監聽onload事件,每次onload代表每個片段讀取完畢,我們只需要在onload中計算已讀取的百分比就可以了!
var bar2 = document.getElementById('progress-bar2'); var progress2 = document.getElementById('progress2'); var input6 = document.getElementById('file6'); var block = 1 * 1024 * 1024; // 每次讀取1M // 當前文件對象 var file; // 當前已讀取大小 var fileLoaded; // 文件總大小 var fileSize; // 每次讀取一個block function readBlob2() { var blob; if(file.webkitSlice) { blob = file.webkitSlice(fileLoaded, fileLoaded + block + 1); } else if(file.mozSlice) { blob = file.mozSlice(fileLoaded, fileLoaded + block + 1); } else if(file.slice) { blob = file.slice(fileLoaded, fileLoaded + block + 1); } else { alert('不支持分段讀取!'); return false; } reader.readAsBinaryString(blob); } // 每個blob讀取完畢時調用 function loadHandler2(e) { fileLoaded += e.total; var percent = fileLoaded / fileSize; if(percent < 1) { // 繼續讀取下一塊 readBlob2(); } else { // 結束 percent = 1; } percent = Math.ceil(percent * 100) + '%'; progress2.innerHTML = percent; progress2.style.width = percent; } function fileSelect6(e) { file = this.files[0]; if(!file) { alert('文件不能為空!'); return false; } fileLoaded = 0; fileSize = file.size; bar2.style.display = 'block'; // 開始讀取 readBlob2(); } var reader = new FileReader(); // 只需監聽onload事件 reader.onload = loadHandler2; input6.onchange = fileSelect6
注意事項
在chrome瀏覽器上測試時,如果直接以file://xxx這種形式訪問demo,會出現FileReader讀取不到內容的情況,表現為FileReader的result為空或者FileReader根本就沒有去讀取文件內容,FileReader各個事件沒有觸發;
這種情況我想應該是類似於chrome不允許添加本地cookie那樣,chrome也不允許以file://xxx這種頁面上的js代碼訪問文件內容;
解決辦法很簡單,只需要將測試文件放到一個web服務器上,以http://xxx形式訪問即可。
參考文章
轉:http://hushicai.com/2014/03/29/html5-du-qu-ben-di-wen-jian.html