对处理文件的操作一直停留在框架层面,还没有完全手动的去处理过,跟着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; }
|
默认的<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
的for
属性来将label
和input
绑定。点击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; }
|
然后给元素添加dragenter
、dragover
、drop
事件监听来处理文件:
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); } }
|
积累!💪