web应用程序中使用文件
通过使用在 HTML5 中加入到 DOM 的 File API,使在 web 内容中让用户选择本地文件然后读取这些文件的内容成为可能。用户可以通过 HTML 中的元素或者是通过拖拽来选择本地文件。
访问被选择的文件
考虑这段 HTML:
<input type="file" id="input">
通过 File API,我们可以访问 FileList,它包含了表示用户所选文件的 File 对象
如果用户只选择了一个文件,那么只需要考虑列表中的第一个文件。
使用传统的 DOM 选择器访问一个被选择的文件:
const selectedFile = document.getElementById('input').files[0];
通过 change 事件访问被选择的文件]
可以(但不是强制的)通过 change 事件访问 FileList
<input type="file" id="input" onchange="handleFiles(this.files)">
当用户选择一个文件时,handleFiles() 方法会用一个 FileList 对象作为参数被调用,FileList对象包含表示用户选择的文件的 File 对象。
如果你想让用户选择多个文件,只需在 input 元素上使用 multiple 属性:
<input type="file" id="input" multiple onchange="handleFiles(this.files)">
在这个例子中,对于每个用户选择的文件,传递给 handleFiles()方法的文件列表都包含一个对应的 File对象。
通过 click() 方法使用隐藏的 file input 元素
你可以隐藏公认难看的 file 元素并显示你自己的界面来打开文件选择器,然后显示哪个或哪些文件被用户选中了。你可以通过给 input 元素添加 display:none 的样式,再调用元素的 click() 方法来实现。
考虑这段 HTML:
<input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<button id="fileSelect">Select some files</button>
处理 click 事件的代码类似于这样:
const fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem");
fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
}, false);
使用 label 元素来触发一个隐藏的 file input 元素
允许在不使用 JavaScript(click() 方法)来打开文件选择器,可以使用 label 元素。注意在这种情况下,input 元素不能使用 display: none(或 visibility: hidden)隐藏,否则 label 将无法通过键盘访问。使用 visually-hidden technique作为替代。
考虑这段 HTML:
<input type="file" id="fileElem" multiple accept="image/*" class="visually-hidden">
<label for="fileElem">Select some files</label>
和这段 CSS:
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
/* Separate rule for compatibility, :focus-within is required on modern Firefox and Chrome */
input.visually-hidden:focus + label {
outline: thin dotted;
}
input.visually-hidden:focus-within + label {
outline: thin dotted;
}
这里不需要添加任何 JavaScript 代码来调用fileElem.click(),另外,这时你也可以给 label 元素添加你想要的样式。您需要在其 label 上提供隐藏 input 字段的焦点状态的视觉提示,比如上面用的轮廓,或者背景颜色或边框阴影。(截至编写时为止,Firefox 不显示 <input type="file"> 元素的视觉提示。)
使用拖放来选择文件
你还可以让用户将文件拖拽到你的网页应用中。
第一步是创建一个 drop 区域。虽然你网页内容的哪部分接受拖放取决于你的应用设计,但是使一个元素接收 drop 事件是很容易的。
let dropbox;
dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
在这个例子中,我们将 ID 为dropbox的元素变为了我们的 drop 区域。这是通过给元素添加dragenter, dragover, 和drop事件监听器实现的。
我们其实并不需要对dragenter and dragover 事件进行处理,所以这些函数都很简单。他们只需要包括禁止事件传播和阻止默认事件:
function dragenter(e) {
e.stopPropagation();
e.preventDefault();
}
function dragover(e) {
e.stopPropagation();
e.preventDefault();
}
真正的奥妙在drop()这个函数中:
function drop(e) {
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
handleFiles(files);
}
这里,我们从事件中获取到了dataTransfer 这个域,然后从中得到文件列表,再将它们传递给handleFiles()函数。在这之后,处理文件的方法和用input元素或者用拖拽就是一样的了。
显示用户选择的图片的缩略图
比方说,你正在开发一个炫酷的下一代图片分享网站,并且想使用 HTML5 来展示用户在实际上传之前的图片的缩略图。你可以像我们之前讨论的那样创建自己的 input 元素或者 drop 区域,然后对他们使用一个回调函数,比如下面的handleFiles()。
function handleFiles(files) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
var imageType = /^image\//;
if (!imageType.test(file.type)) {
continue;
}
var img = document.createElement("img");
img.classList.add("obj");
img.file = file;
preview.appendChild(img); // 假设"preview"就是用来显示内容的 div
var reader = new FileReader();
reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
reader.readAsDataURL(file);
}
}
这里我们循环处理用户选择的文件,看每个文件的type属性是不是 image(通过正则表达式来匹配 MIME 类型字符串模式"image/*")。对每个文件而言,如果它是 image,我们就创建一个新的img元素。可以使用 css 来创建一个漂亮的边框或阴影来显示图片的具体大小,在这儿就不具体做了。
为了在 DOM 树中更容易地找到他们,每个图片元素都被添加了一个名为obj的 CSS 类。我们还给每个图片添加了file属性使它具有 File;这样做可以让我们拿到稍后需要实际上传的图片。我们在预览页中使用 Node.appendChild()来添加新的缩略图。
接下来,我们创建了FileReader来处理异步的图片加载并把他赋给img元素。在创建一个新的 FileReader对象后,我们新建了它的onload 函数,然后调用readAsDataURL()函数开始后台读取文件。当整个图片文件的内容都被全部加载完后,它们被转换成了一个被传递到onload回调函数的data:URL。我们再执行常规操作将img元素的src属性设置为刚刚加载完毕的 URL,使得图像可以显示在用户屏幕上的缩略图中。
使用对象 URL
Gecko 2.0 引入了对 DOM [window.URL.createObjectURL() (en-US)]和 [window.URL.revokeObjectURL() (en-US)]方法的支持。这使得你可以创建用于引用任何数据的简单 URL 字符串,也可以引用一个包括用户电脑上的本地文件的 DOM [File]对象。
当你需要在 HTML 中通过 URL 来引用一个[File]对象时,你可以创建一个对象 URL,就像这样:
var objectURL = window.URL.createObjectURL(fileObj);
这个对象 URL 是一个标识[File]对象的字符串。每次你调用[window.URL.createObjectURL() (en-US)],就会产生一个唯一的对象 URL,即使是你对一个已创建了对象 URL 的文件再次创建一个对象 URL。每个创建了的对象 URL 必须要释放。当文档关闭时,它们会自动被释放。如果你的网页要动态使用它们,你需要显式调用 [window.URL.revokeObjectURL() (en-US)]来释放它们:
window.URL.revokeObjectURL(objectURL);
使用对象 URL 来显示图片
这个例子使用对象 URL 来显示图片缩略图。另外,示例也会显示文件名和文件大小等其他文件信息。
显示接口的 HTML 类似于这样:
<input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<a href="#" id="fileSelect">Select some files</a>
<div id="fileList">
<p>No files selected!</p>
</div>
这确定我们的文件 [``] 元素显示为一个可以调用文件选择器的链接(我们隐藏了文件输入元素来阻止显示用户不友好的界面)。这个在 [通过 click() 方法使用隐藏的 file input 元素]已经说明了这种调用文件选择器的方法。
handleFiles() 方法如下:
window.URL = window.URL || window.webkitURL;
var fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem"),
fileList = document.getElementById("fileList");
fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
}, false);
function handleFiles(files) {
if (!files.length) {
fileList.innerHTML = "<p>No files selected!</p>";
} else {
fileList.innerHTML = "";
var list = document.createElement("ul");
fileList.appendChild(list);
for (var i = 0; i < files.length; i++) {
var li = document.createElement("li");
list.appendChild(li);
var img = document.createElement("img");
img.src = window.URL.createObjectURL(files[i]);
img.height = 60;
img.onload = function () {
window.URL.revokeObjectURL(this.src);
}
li.appendChild(img);
var info = document.createElement("span");
info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
li.appendChild(info);
}
}
}
首先,获取 ID 为 fileList 的 [div]。这个区块里我们会插入我们的文件列表,包括缩略图。
如果传入 handleFiles() 的 [FileList]对象值为 null 时,我们只要简单将这块的内部 HTML 为显示“No files selected!”。否则,我们就需要像下面这样编写我们的文件列表:
-
创建一个无序列表 ([
ul] 元素。 -
通过调用列表的[
Node.appendChild()]方法来将新的列表元素插入到 [ul``]块。 -
遍历文件集合
FileList(即files)中的每个File:- 创建一个新的列表项([
li])元素并插入到列表中。 - 创建一个新的图片([
img]元素。 - 设置图片的源为一个新的指代文件的对象 URL,使用[
window.URL.createObjectURL()(en-US)]来创建 blob URL。 - 设置图片的高度为 60 像素。
- 设置图片的 load 事件处理器来释放对象 URL,当图片加载完成之后对象 URL 就不再需要了。这个可以通过调用window.URL.revokeObjectURL()方法并且传递
img.src中的对象 URL 字符串来实现。 - 将新的列表项添加到列表中。
- 创建一个新的列表项([
上传一个用户选择的文件
另一件您可能想要做的事是让用户将选定的一个或多个文件(例如前一个示例中选择的图像)上传到服务器。这用异步可以很容易地完成。
创建上传任务
继续前面示例中构建缩略图的代码,回想一下每个缩略图图像都在 CSS 类obj中,并且file属性中附加了相应的 File 。这允许我们使用 Document.querySelectorAll() 选择用户决定上传的所有图像,如下所示:
function sendFiles() {
var imgs = document.querySelectorAll(".obj");
for (var i = 0; i < imgs.length; i++) {
new FileUpload(imgs[i], imgs[i].file);
}
}
第 2 行获取了文档中所有 CSS 类为obj的元素的 NodeList,命名为imgs。在我们的例子中,这些是包含所有图像缩略图的列表。有了这个列表,遍历并为每一项创建一个新的FileUpload实例就很简单了。每个实例都可以处理相应文件的上传。
处理文件的上传过程
FileUpload函数接受两个输入:一个 image 元素和一个包含图像数据的文件。
function FileUpload(img, file) {
var reader = new FileReader();
this.ctrl = createThrobber(img);
var xhr = new XMLHttpRequest();
this.xhr = xhr;
var self = this;
this.xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var percentage = Math.round((e.loaded * 100) / e.total);
self.ctrl.update(percentage);
}
}, false);
xhr.upload.addEventListener("load", function(e){
self.ctrl.update(100);
var canvas = self.ctrl.ctx.canvas;
canvas.parentNode.removeChild(canvas);
}, false);
xhr.open("POST", "http://demos.hacks.mozilla.org/paul/demos/resources/webservices/devnull.php");
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
reader.onload = function(evt) {
xhr.send(evt.target.result);
};
reader.readAsBinaryString(file);
}
上面的FileUpload() 函数创建了一个“加载中”指示器,用于显示进度信息,然后创建了一个 XMLHttpRequest来处理上传数据。
实际传输数据前,采取了几道准备步骤:
XMLHttpRequest的progress监听器被设为将加载指示器更新为新的百分比信息,这样随着上传进行,指示器会显示最新的信息。XMLHttpRequest的load事件监听器被设为将加载指示器的进度信息更新为 100%,以保证进度显示确实达到了 100%(以防在上传过程中出现粒度误差)。然后它移除了已经不再需要的加载指示器。这样上传一完成指示器就会消失。- 上传图像文件的请求,是由调用
XMLHttpRequest的open()函数发送 POST 请求完成的。 - 上传的 MIME 类型是通过调用
XMLHttpRequest的overrideMimeType()函数来设置的。这个例子中,我们使用通用 MIME 类型。是否需要设置 MIME 类型取决于你的具体使用情况。 FileReader对象用于将文件转换为二进制字符串。- 最后,当内容被加载时,会调用
XMLHttpRequest的send()函数来上传文件内容。
备注: 上面例子中使用的非标准的sendAsBinary方法在 Gecko 31 中已废弃。请使用标准的send(Blob data)方法代替。
异步处理文件上传
这个例子演示了如何异步上传文件,在服务器端使用了 php、在客户端使用了 JavaScript。
<?php
if (isset($_FILES['myFile'])) {
// Example:
move_uploaded_file($_FILES['myFile']['tmp_name'], "uploads/" . $_FILES['myFile']['name']);
exit;
}
?><!DOCTYPE html>
<html>
<head>
<title>dnd binary upload</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="application/javascript">
function sendFile(file) {
const uri = "/index.php";
const xhr = new XMLHttpRequest();
const fd = new FormData();
xhr.open("POST", uri, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText); // handle response.
}
};
fd.append('myFile', file);
// Initiate a multipart/form-data upload
xhr.send(fd);
}
window.onload = function() {
const dropzone = document.getElementById("dropzone");
dropzone.ondragover = dropzone.ondragenter = function(event) {
event.stopPropagation();
event.preventDefault();
}
dropzone.ondrop = function(event) {
event.stopPropagation();
event.preventDefault();
const filesArray = event.dataTransfer.files;
for (let i=0; i<filesArray.length; i++) {
sendFile(filesArray[i]);
}
}
}
</script>
</head>
<body>
<div>
<div id="dropzone" style="margin:30px; width:500px; height:300px; border:1px dotted grey;">Drag & drop your file here...</div>
</div>
</body>
</html>
用对象 URL 显示 PDF
对象 URL 可以用于 image 之外的其它东西!它可以用于显示嵌入的 PDF 文件或任何其它浏览器能显示的资源。
在 Firefox 中,要让 PDF 嵌入式地显示在 iframe 中(而不是作为下载的文件弹出),必须将pdfjs.disabled设为false 非标准.
<iframe id="viewer">
Copy to Clipboard
这是src属性的改动:
const obj_url = window.URL.createObjectURL(blob);
const iframe = document.getElementById('viewer');
iframe.setAttribute('src', obj_url);
window.URL.revokeObjectURL(obj_url);
将对象 URL 用于其它文件类型
你可以用同样方式操作其它格式的文件。这是预览上传的视频的方法:
const video = document.getElementById('video');
const obj_url = window.URL.createObjectURL(blob);
video.src = obj_url;
video.play()
window.URL.revokeObjectURL(obj_url);