如何使用File APIs来读取文件

兼容性检查

1
2
3
4
5
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”属性设为数据网址来呈现缩略图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<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对象包含的信息
    1
    2
    3
    4
    5
    6
    7
    8
    {
    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对象获取:

  1. <input type="file">的元素
  2. 拖拽操作的DataTransfer对象
  3. 通过在一个HTMLCanvasElement上调用mozGetAsFile() API

通过input来选择文件

1
2
3
4
5
6
7
/* 假设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对象:

1
2
3
for (var i = 0; fileCount = fileList.length; i < fileCount; i++) {
console.log(fileList[i]);
}

但是我们也可以通过其他方式来使用forEach()方法:

1
2
3
4
5
6
7
8
9
//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(目标放置到有效的拖拽区域时)

其中需要注意两点:

  1. 如果dragover事件不阻止默认事件,drop事件就不会被触发。
  2. dragexit和dragleave在不同浏览器中的触发存在差异,dragexit在Chrome浏览器中就永远不会被触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//拖拽和显示区域
<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>