--- title: 在web应用程序中使用文件 slug: Web/API/File/Using_files_from_web_applications tags: - File - File API - ajax上传 - 文件 - 文件上传 translation_of: Web/API/File/Using_files_from_web_applications ---
{{APIRef("File API")}}
通过使用在 HTML5 中加入到 DOM 的 File API,使在web内容中让用户选择本地文件然后读取这些文件的内容成为可能。用户可以通过HTML中的 {{HTMLElement("input/file", '<input type="file">')}} 元素或者是通过拖拽来选择本地文件。
如果你想通过扩展或者其它的chrome源码浏览器(browser chrome code)使用DOM File API,是可行的;然而,需要注意有一些附加特性。详细请见 Using the DOM File API in chrome code。
考虑这段 HTML:
<input type="file" id="input">
通过 File API,我们可以访问 {{DOMxRef("FileList")}},它包含了表示用户所选文件的 {{DOMxRef("File")}} 对象
如果用户只选择了一个文件,那么只需要考虑列表中的第一个文件。
使用传统的 DOM 选择器访问一个被选择的文件:
const selectedFile = document.getElementById('input').files[0];
可以(但不是强制的)通过 change
事件访问 {{DOMxRef("FileList")}}:
<input type="file" id="input" onchange="handleFiles(this.files)">
当用户选择一个文件时,handleFiles()
方法会用一个 {{DOMxRef("FileList")}} 对象作为参数被调用,{{DOMxRef("FileList")}} 对象包含表示用户选择的文件的 {{DOMxRef("File")}} 对象。
如果你想让用户选择多个文件,只需在 input
元素上使用 multiple
属性:
<input type="file" id="input" multiple onchange="handleFiles(this.files)">
在这个例子中,对于每个用户选择的文件,传递给 handleFiles()
方法的文件列表都包含一个对应的 {{DOMxRef("File")}} 对象。
你需要使用 {{DOMxRef("EventTarget.addEventListener()")}} 去添加 change
事件监听器,像这样:
const inputElement = document.getElementById("input"); inputElement.addEventListener("change", handleFiles, false); function handleFiles() { const fileList = this.files; /* now you can work with the file list */ }
注意在这个例子里,handleFiles()
方法本身是一个事件处理器,不像之前的例子中,它被事件处理器调用然后传递给它一个参数。
DOM 提供 {{ domxref("FileList") }} 对象列出了用户选择的所有文件,每一个文件被指定为一个 {{ domxref("File") }} 对象。你可以通过检查文件列表 {{ domxref("FileList") }} 的 length
属性来确定用户选择了多少个文件:
const numFiles = files.length;
可以像数组一样简单地访问文件列表来检索各个 {{ domxref("File") }} 对象:
for (let i = 0, numFiles = files.length; i < numFiles; i++) { const file = files[i]; // ... }
这个循环遍历了文件列表里的所有文件。
{{ domxref("File") }} 对象提供了三个属性,包含了文件的有用信息。
name
size
type
""
。下面的例子展示了 size
属性的一种可能用法:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>File(s) size</title> <script> function updateSize() { let nBytes = 0, oFiles = document.getElementById("uploadInput").files, nFiles = oFiles.length; for (let nFileId = 0; nFileId < nFiles; nFileId++) { nBytes += oFiles[nFileId].size; } let sOutput = nBytes + " bytes"; // optional code for multiples approximation const aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) { sOutput = nApprox.toFixed(3) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)"; } // end of optional code document.getElementById("fileNum").innerHTML = nFiles; document.getElementById("fileSize").innerHTML = sOutput; } </script> </head> <body onload="updateSize()"> <form name="uploadForm"> <div> <input id="uploadInput" type="file" name="myFiles" onchange="updateSize();" multiple> selected files: <span id="fileNum">0</span>; total size: <span id="fileSize">0</span> </div> <div><input type="submit" value="Send file"></div> </form> </body> </html>
你可以隐藏公认难看的 file {{ HTMLElement("input") }} 元素并显示你自己的界面来打开文件选择器,然后显示哪个或哪些文件被用户选中了。你可以通过给 input 元素添加 display:none
的样式,再调用 {{HTMLElement("input") }} 元素的 {{domxref("element.click","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);
你可以给这个用来打开文件选择器的新按钮添加任何你想要的样式。
允许在不使用 JavaScript(click() 方法)来打开文件选择器,可以使用 {{ HTMLElement("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区域。这是通过给元素添加{{event('dragenter')}}, {{event('dragover')}}, 和{{event('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
属性使它具有 {{ domxref("File") }};这样做可以让我们拿到稍后需要实际上传的图片。我们在预览页中使用 {{ domxref("Node.appendChild()") }}来添加新的缩略图。
接下来,我们创建了{{ domxref("FileReader") }}来处理异步的图片加载并把他赋给img
元素。在创建一个新的 FileReader
对象后,我们新建了它的onload
函数,然后调用readAsDataURL()
函数开始后台读取文件。当整个图片文件的内容都被全部加载完后,它们被转换成了一个被传递到onload
回调函数的data:
URL。我们再执行常规操作将img
元素的src
属性设置为刚刚加载完毕的URL,使得图像可以显示在用户屏幕上的缩略图中。
Gecko 2.0 {{ geckoRelease("2.0") }}引入了对DOM {{ domxref("window.URL.createObjectURL()") }} 和 {{ domxref("window.URL.revokeObjectURL()") }} 方法的支持。这使得你可以创建用于引用任何数据的简单URL字符串,也可以引用一个包括用户电脑上的本地文件的DOM {{ domxref("File") }}对象。
当你需要在HTML中通过URL来引用一个{{ domxref("File") }}对象时,你可以创建一个对象URL,就像这样:
var objectURL = window.URL.createObjectURL(fileObj);
这个对象URL是一个标识{{ domxref("File") }}对象的字符串。每次你调用{{ domxref("window.URL.createObjectURL()") }},就会产生一个唯一的对象URL,即使是你对一个已创建了对象URL的文件再次创建一个对象URL。每个创建了的对象URL必须要释放。当文档关闭时,它们会自动被释放。如果你的网页要动态使用它们,你需要显式调用 {{ domxref("window.URL.revokeObjectURL()") }}来释放它们:
window.URL.revokeObjectURL(objectURL);
这个例子使用对象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>
这确定我们的文件 {{ HTMLElement("input") }} 元素显示为一个可以调用文件选择器的链接(我们隐藏了文件输入元素来阻止显示用户不友好的界面)。这个在 通过 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
的 {{ HTMLElement("div") }} 。这个区块里我们会插入我们的文件列表,包括缩略图。
如果传入 handleFiles()
的 {{ domxref("FileList") }} 对象值为 null
时,我们只要简单将这块的内部HTML为显示“No files selected!”。否则,我们就需要像下面这样编写我们的文件列表:
files
)中的每个 {{ domxref("File") }}:
img.src
中的对象URL字符串来实现。这是上面代码的一个在线示例:
{{EmbedLiveSample('示例:使用对象URL来显示图片', '100%', '300px')}}
另一件您可能想要做的事是让用户将选定的一个或多个文件(例如前一个示例中选择的图像)上传到服务器。这用异步可以很容易地完成。
继续前面示例中构建缩略图的代码,回想一下每个缩略图图像都在CSS类obj
中,并且file
属性中附加了相应的 {{ domxref("File") }} 。这允许我们使用 {{ domxref("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
的元素的 {{ domxref("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()
函数创建了一个“加载中”指示器,用于显示进度信息,然后创建了一个 {{ domxref("XMLHttpRequest") }} 来处理上传数据。
实际传输数据前,采取了几道准备步骤:
XMLHttpRequest
的progress
监听器被设为将加载指示器更新为新的百分比信息,这样随着上传进行,指示器会显示最新的信息。XMLHttpRequest
的load
事件监听器被设为将加载指示器的进度信息更新为100%,以保证进度显示确实达到了100%(以防在上传过程中出现粒度误差)。然后它移除了已经不再需要的加载指示器。这样上传一完成指示器就会消失。XMLHttpRequest
的open()
函数发送POST请求完成的。XMLHttpRequest
的overrideMimeType()
函数来设置的。这个例子中,我们使用通用MIME类型。是否需要设置MIME类型取决于你的具体使用情况。FileReader
对象用于将文件转换为二进制字符串。XMLHttpRequest
的send()
函数来上传文件内容。sendAsBinary
方法在Gecko 31 {{ geckoRelease(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可以用于image之外的其它东西!它可以用于显示嵌入的PDF文件或任何其它浏览器能显示的资源。
在Firefox中,要让PDF嵌入式地显示在iframe中(而不是作为下载的文件弹出),必须将pdfjs.disabled
设为false
{{non-standard_inline()}}.
<iframe id="viewer">
这是src
属性的改动:
const obj_url = window.URL.createObjectURL(blob); const iframe = document.getElementById('viewer'); iframe.setAttribute('src', obj_url); window.URL.revokeObjectURL(obj_url);
你可以用同样方式操作其它格式的文件。这是预览上传的视频的方法:
const video = document.getElementById('video'); const obj_url = window.URL.createObjectURL(blob); video.src = obj_url; video.play() window.URL.revokeObjectURL(obj_url);
规范 | 状态 | 注解 |
---|---|---|
{{SpecName('HTML WHATWG', 'number-state.html#concept-input-type-file-selected', 'File upload state')}} | {{Spec2('HTML WHATWG')}} | |
{{SpecName('File API')}} | {{Spec2('File API')}} | Initial definition |