常見的語言好比php、shell等,是如何讀取文件的呢?php
實際上,大多數語言都須要先獲取文件句柄,而後調用文件訪問接口,打開文件句柄,讀取文件!html
那麼,HTML5是否也是這樣的呢?html5
答案是確定的!web
HTML5爲咱們提供了一種與本地文件系統交互的標準方式:File Api。chrome
該規範主要定義瞭如下數據結構:shell
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
,這一般發生在表單選擇文件或者拖拽文件。cookie
表單提交文件是最多見的場景,用戶選擇文件後,觸發了文件選擇框的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
還提供如下事件:
一旦調用了以上某個方法讀取文件後,咱們能夠監聽以上任何一個事件來得到進度、結果等。
這裏主要用到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形式訪問便可。
感謝分享!