相容性檢查
if (window.File && window.FileReader && window.FileList && window.Blob) {
//支援File APIs
} else {
//不支援File APIs
}
複製程式碼
FileReader()
FileReader物件讓web應用程式可以非同步地讀取儲存在使用者電腦上的檔案(或者原始資料緩衝區)的內容。在JavaScript中,FileReaderd物件通過傳入兩種相應的物件(File和Blob)來進行資料的讀取,而且這個方法在Web Workers中也能使用。
FileReader 包括四個非同步讀取檔案的選項:
FileReader.readAsBinaryString(Blob|File)
- 返回值的result 屬性將包含二進位制字串形式的file/blob 資料。每個位元組均由一個 [0..255] 範圍內的整數表示。FileReader.readAsText(Blob|File, opt_encoding)
- 返回值的result 屬性將包含文字字串形式的 file/blob 資料。該字串在預設情況下采用“UTF-8”編碼。使用可選編碼引數可指定其他格式。FileReader.readAsDataURL(Blob|File)
- 返回值的result 屬性將包含編碼為資料網址的 file/blob 資料。FileReader.readAsArrayBuffer(Blob|File)
- 返回值的result 屬性將包含 ArrayBuffer 物件形式的 file/blob 資料。
FileReader 物件呼叫其中某一種讀取方法後,可使用 onloadstart、onprogress、onload、onabort、onerror 和 onloadend
跟蹤其進度。
讀取檔案並顯示進度
下面的示例從使用者選擇的內容中過濾掉了圖片,對檔案呼叫 reader.readAsDataURL()
,並通過將“src”屬性設為資料網址來呈現縮圖。
<style>
.thumb {
height: 75px;
border: 1px solid #000;
margin: 10px 5px 0 0;
}
#list {
border: 1px solid lightgrey;
padding: 15px;
text-align: center;
}
#progress_bar {
margin: 10px 0;
padding: 3px;
border: 1px solid #000;
font-size: 14px;
clear: both;
opacity: 0;
-moz-transition: opacity 1s linear;
-o-transition: opacity 1s linear;
-webkit-transition: opacity 1s linear;
}
#progress_bar.loading {
opacity: 1.0;
}
#progress_bar .percent {
background-color: #99ccff;
height: auto;
width: 0;
}
</style>
<body>
<input type="file" name="files[]" id="files" multiple />
<div id="list"></div>
<button onclick="abortRead();">Cancel read</button>
<script>
let reader;
let progress;
let progress_bar;
function abortRead() {
reader.abort();
}
function errorHandler(evt) {
let error = evt.target.error;
switch (error.code) {
case error.NOT_FOUND_ERR:
alert('沒有找到檔案');
break;
case error.NOT_READABLE_ERR:
alert('無法讀取檔案');
break;
case error.ABORT_ERR:
break;
default:
alert('檔案讀取錯誤');
}
}
function updateProgress(evt) {
if (evt.lengthComputable) {
let percentLoaded = Math.round((evt.loaded / evt.total) * 100);
if (percentLoaded < 100) {
progress.style.width = percentLoaded + '%';
progress.textContent = percentLoaded + '%';
}
}
}
function handleFileSelect(evt) {
let files = evt.target.files;
//建立進度條
progress_bar = document.createElement('div');
progress_bar.id = 'progress_bar';
progress = document.createElement('div');
progress.className = 'percent';
progress.style.width = '0%';
progress.textContent = '0%';
progress_bar.appendChild(progress);
document.getElementById('list').appendChild(progress_bar);
for (let i = 0; i < files.length; i++) {
reader = new FileReader();
if (!files[i].type.match('image.*')) {
alert('選擇的檔案不是圖片');
abortRead();
return;
}
reader.onerror = errorHandler;
reader.onprogress = updateProgress;
reader.onabort = (e) => {
alert('檔案讀取已取消');
};
reader.onloadstart = (e) => {
progress_bar.className = 'loading';
};
reader.onload = (e) => {
let span = document.createElement('span');
span.innerHTML = ['<img class="thumb" src="', e.target.result, '" title="', files[i].name, '"/>'].join('');
document.getElementById('list').insertBefore(span, progress_bar);
progress.style.width = '100%';
progress.textContent = '100%';
};
reader.readAsDataURL(files[i]);
}
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
</body>
複製程式碼
See the Pen FileReader Demo by Lu (@smallbone) on CodePen.
FileList API
字面上可以理解為多個File物件組合成的陣列,但是隻有length屬性
和item(index)
方法,訪問其中的File物件既可以使用files.item(index)
,也可以使用files[index]
的方法。
File API
File物件是一種特定型別的Blob。FileReader
, URL.createObjectURL()
, createImageBitmap()
, 以及XMLHttpRequest.send()
都接受Blobs和Files。
- File物件包含的資訊
{
lastModified: 1428005315000,
lastModifiedDate: Thu Apr 02 2015 15:08:35 GMT-0500 (CDT),
name: "profile.pdf",
size: 135568,
type: "application/pdf",
webkitRelativePath: ""
}
複製程式碼
需要注意的是,type是根據副檔名來判斷的,所以並不是很可靠。根據上面File物件的資訊其實就可以實現一些常用的功能了,比如限制檔案上傳的大小,初步的限制檔案上傳的型別(當然也可以通過input元素的accept屬性來實現,但是最終的型別驗證還是需要在伺服器端實現)。
File物件一般通過以下途徑返回的FileList物件獲取:
<input type="file">
的元素- 拖拽操作的DataTransfer物件
- 通過在一個HTMLCanvasElement上呼叫
mozGetAsFile() API
通過input來選擇檔案
/* 假設input元素為<input type="file" id="upload" multiple> */
//multiple表示一次支援多個檔案上傳
let uploadInput = document.getElementById('upload');
uploadInput.addEventListener('change', ()=>{
let fileList = uploadInput.files;
console.log(fileList);
});
複製程式碼
由於FileList物件並沒有forEach()
方法,所以一般需要通過for迴圈
來遍歷其中的每個File物件:
for (var i = 0; fileCount = fileList.length; i < fileCount; i++) {
console.log(fileList[i]);
}
複製程式碼
但是我們也可以通過其他方式來使用forEach()
方法:
//1.call方法
[].forEach.call(fileList, (file, i, fileList)=>{
...
});
//2.ES6方法
Array.from(uploadInput).forEach((i)=>{
...
});
複製程式碼
通過拖拽(drag&drop)選擇檔案
拖拽事件:
- drag(開始拖動,持續事件)
- dragend(釋放滑鼠或者按下ESC,結束拖動)
- dragenter(進入有效的拖拽區域時)
- dragexit(當一個元素不再是拖動操作的直接選擇目標時)
- dragleave(離開有效的拖拽區域時)
- dragover(懸停在有效的拖拽區域內時,持續事件)
- dragstart(開始拖動)
- drop(目標放置到有效的拖拽區域時)
其中需要注意兩點:
- 如果dragover事件不阻止預設事件,drop事件就不會被觸發。
- dragexit和dragleave在不同瀏覽器中的觸發存在差異,dragexit在Chrome瀏覽器中就永遠不會被觸發。
//拖拽和顯示區域
<div id="drop_zone">Drop files here</div>
<output id="list"></output>
<script>
function handleFileSelect(evt) {
evt.stopPropagation();
evt.preventDefault();
//注意這裡不再是target.files
let files = evt.dataTransfer.files;
let output = [];
[].forEach.call(files, (file)=>{
output.push('<li><strong>', file.name, '</strong> (', file.type || 'n/a', ') - ', (file.size/1024).toFixed(3), ' Kb, last modified date: ', file.lastModifiedDate.toLocaleDateString(), '</li>');
});
document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
}
function handleDragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
evt.dataTransfer.dropEffect = 'copy';
}
let dropZone = document.getElementById('drop_zone');
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileSelect, false);
</script>
複製程式碼