HTML5讀取本地文件


本文轉自:轉: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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM