0%

Web 应用程序中处理文件

对处理文件的操作一直停留在框架层面,还没有完全手动的去处理过,跟着MDN这篇文章:在web应用程序中使用文件 走一遍。

访问被选择的文件

1
2
3
4
5
6
<input 
id="input"
onchange="handleFiles()"
type="file"
placeholder="上传文件"
/>

通过change事件访问被选择的文件:

1
2
3
4
5
6
7
const inputElement = document.getElementById('input');
inputElement.addEventListener('change', handleFiles, false);

function handleFiles() {
const fileList = this.files;
console.log('file list', this.files);
}

如果想上传多个文件,只需要在input元素加上multiple属性:

1
2
3
4
5
6
7
<input 
id="input"
onchange="handleFiles()"
multiple
type="file"
placeholder="上传文件"
/>

访问被选择的文件信息

DOM提供FileList对象列出了用户选择的所有文件,每一个文件被指定为一个File对象。FileList是一个对象,但它有个length属性,我们可知道有多少个文件,可以用for循环来读取文件信息:

1
2
3
for (let i = 0, numFiles = fileList.length; i < numFiles; i ++) {
const file = fileList[i];
}

File对象提供了三个属性,包含了文件有用的信息:

  • name 文件名称,只读字符串。
  • size 文件大小,单位为字节,只读的64位整数。
  • type 文件的MIME类型,只读字符串,当类型不确定时为""

这样我们修改下方法,显示上传的文件大小:

1
2
3
4
5
6
7
8
<input 
id="input"
multiple
type="file"
placeholder="上传文件"
/>
<div id="totalSize"></div>
<div id="fileNum"></div>
1
2
3
4
5
6
7
8
9
10
11
function handleFiles() {
const fileList = this.files;
let nBytes = 0, nFiles = fileList.length;

for (let i = 0; i < nFiles; i ++) {
const file = fileList[i];
nBytes += file.size;
}
document.getElementById('totalSize').innerHTML = nBytes;
document.getElementById('fileNum').innerHTML = nFiles;
}

通过click()方法使用隐藏的file input元素

默认的<input type="file"/>是公认的难看,我们希望可以自定义界面,那么可以隐藏掉这个选择图片的input,然后可以通过按钮来启动选择文件:

1
2
3
4
5
6
7
8
<input
style="display: none;"
id="input"
multiple
type="file"
placeholder="上传文件"
/>
<button id="fileSelect">选择文件</button>

然后给这个按钮加上点击事件,当点击后触发file input

1
2
3
4
5
6
7
8
9
10
const inputElement = document.getElementById('input');
inputElement.addEventListener('change', handleFiles, false);

const fileSelectElement = document.getElementById('fileSelect');
fileSelectElement.addEventListener('click', function (e) {
if (!inputElement) {
return;
}
inputElement.click();
});

使用label元素来触发隐藏的file input

可以使用labelfor属性来将labelinput绑定。点击label相当于点击了file input元素的click。但是要注意在这种情况下,不可以使用dispaly: none;来隐藏,只需要视觉不可见即可:

1
2
3
4
<div>
<input class="visually-hidden" id="input" multiple type="file" placeholder="上传文件" />
<label for="input">上传文件</label>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
.visually-hidden{
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
input.visually-hidden:focus + label{
outline: thin dotted;
}
input.visually-hidden:focus-within + label{
outline: thin dotted;
}

使用拖放来选择文件

我们可以让程序支持拖动上传文件。首先创建一个drop区域:

1
2
3
4
5
<div id="dropbox">
<p>
拖动文件到这个区域
</p>
</div>
1
2
3
4
5
#dropbox {
width: 300px;
height: 300px;
background: #ccc;
}

然后给元素添加dragenterdragoverdrop事件监听来处理文件:

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
// 拖放
let dropbox = document.getElementById('dropbox');
dropbox.addEventListener('dragenter', dragenter, false);
dropbox.addEventListener('dragover', dragover, false);
dropbox.addEventListener('drop', drop, false);


function dragenter(e) {
e.stopPropagation();
e.preventDefault();
}

function dragover(e) {
e.stopPropagation();
e.preventDefault();
}

function drop(e) {
e.stopPropagation();
e.preventDefault();

var dt = e.dataTransfer;
var files = dt.files;
console.log('fiels', files);
handleFiles(files);
}

在拖动的事件中获取到dataTransfer这个域,然后得到文件列表,再将它们传递给handleFiles()函数,接下来的操作就和点击上传的处理一样了。

显示选择图片的缩略图

前面我们是不限文件,这里我们需要调整下,让选择的文件只能是图片,只需要加上`accept=”image/*”即可:

1
2
3
4
5
6
7
<input 
class="visually-hidden"
accept="image/*"
id="input"
multiple
type="file" placeholder="上传文件"
/>

同时我们需要处理拖动的文件,需要筛选拖动的文件只能是图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function drop(e) {
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
for (let i = 0, nFile = files.length; i < nFile; i ++) {
const imageType = /^image\//;
if (!imageType.test(files[i])) {
alert('拖动的文件有些不是图片');
return;
}
}
handleFiles(files);
}

接下来创建一个预览的区域:

1
<div id="preview"></div>
1
2
3
4
5
6
7
8
9
10
11
#preivew {
width: 300px;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
background: #ccc;
}
#preview img{
width: 100px;
height: 100px;
}

然后处理选择的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

const previewElement = document.getElementById('preview');
const imageType = /^image\//;

function handleFiles(files) {
const fileList = files;
for (let i = 0, numFiles = fileList.length; i < numFiles; i ++) {
const file = fileList[i];
if (!imageType.test(file.type)) {
continue;
}
const image = document.createElement('img');
image.classList.add('obj');
previewElement.appendChild(image);
const reader = new FileReader();
reader.onload = (function(aImg) {
return function(e) {
aImg.src = e.target.result;
};
})(image);
reader.readAsDataURL(file);
}
}

我们是在handleFiles()方法里面通过循环处理FileList里面的文件,先确保是图片类型的文件,然后动态创建<img/>元素,并通过FileReader对象异步的加载图片。

在创建FileReader对象后,创建了onload的回调函数,然后使用readAsDataURL()函数读取文件,当整个图片文件的内容读取完毕后,它会被转换成为一个data:URL并传递给onload的回调函数。在回调函数里面将获得的data:URL设置到img标签的src属性上,这样在选择完毕后就会在页面上显示图片的缩略图。

使用对象URL显示图片

当需要在HTML中通过URL来引用一个File对象时,可以创建一个对象URL

1
var objectURL = window.URL.createObjectURL(fileObj);

可以动态的使用,为了性能和内存的考虑,需要手动去释放他们:

1
window.URL.revokeObjectURL(objectURL);

我们只需要修改前面的for循环里面的内容即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
for (let i = 0, numFiles = fileList.length; i < numFiles; i ++) {
const file = fileList[i];
if (!imageType.test(file.type)) {
continue;
}
const image = document.createElement('img');
image.classList.add('obj');
previewElement.appendChild(image);
image.src = window.URL.createObjectURL(file);
image.onload = function() {
window.URL.revokeObjectURL(this.src);
}
}

用对象URL显示PDF

对象URL可以用于image之外的其他东西!

可以用于显示嵌入的PDF文件或任何其他浏览器能显示的资源文件。

需要先了解下Blob对象。

Blob

Blob对象表示一个不可变的、原始数据的类文件对象。Blob表示的不一定是JavaScript原生格式的数据。File接口基于Blob,继承了Blob的功能并将其扩展使其支持用户系统上的文件。

要从其他非Blob对象和数据构造一个Blob,使用Blob()构造函数,要创建一个Blob数据的子集Blob,需要使用slice()方法。具体参见:MDN Blob

首先我们需要一个pdf的文件Blob,就构造一个本地的选择的pdf文件吧:

1
2
3
4
5
<div>
<input class="visually-hidden" id="input" accept="application/pdf" multiple type="file" placeholder="上传文件" />
<label for="input">上传文件</label>
</div>
<iframe id="pdf" frameborder="0"></iframe>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const inputEl = document.getElementById('input');
inputEl.addEventListener('change', handleFile, false);
const iframeEl = document.getElementById('pdf');
const pdfType = 'application/pdf';

function handleFile() {
const files = this.files;
for(let i = 0, len = files.length; i < len; i ++) {
console.log('type', files[i].type);
if (files[i].type !== pdfType) {
continue;
}
const objURL = window.URL.createObjectURL(files[i]);
iframeEl.setAttribute('src', objURL);
window.URL.revokeObjectURL(objURL);
}
}

积累!💪

码字辛苦,打赏个咖啡☕️可好?💘