常見的語言比如php、shell等,是如何讀取文件的呢?
實際上,大多數語言都需要先獲取文件句柄,然后調用文件訪問接口,打開文件句柄,讀取文件!
那么,HTML5是否也是這樣的呢?
答案是肯定的!
HTML5為我們提供了一種與本地文件系統交互的標准方式:File Api。
該規范主要定義了以下數據結構:
FileFileListBlob
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對象,提供了三個只讀屬性:
lengthComputableloadedtotal
通過以上幾個屬性,即可實時顯示讀取進度,不過需要注意的是,此處的進度條是針對單次讀取的進度,即一次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://www.cnblogs.com/leejersey/p/4772504.html
感謝分享!

