使用HTML5 DOM新增的File API,现在可以让网页要求用户选择本地文件,并且读取这些文件的信息了。选择的方式既可以是HTML<input>
元素,也可以是拖拽 。
你可以在chrome扩展等代码中使用DOM File API ;事实上有些需要注意的额外特性。参考 Using the DOM File API in chrome code 。
访问选中的文件
考虑下面的HTML:
<input type="file" id="input">
通过File API,我们可以在用户选取一个或者多个文件之后,访问到代表了所选文件的一个或多个File
对象,这些对象被包含在一个FileList
对象中.
如果用户只选择了一个文件,那么我们只需要访问这个FileList
对象中的第一个元素.
可以使用传统的DOM选择方法来获取到用户所选择的文件:
var selected_file = document.getElementById('input').files[0];
还可以使用jQuery选择器来选择:
var selectedfile = $('#input').get(0).files[0];
var selectedFile = $('#input')[0].files[0];
如果你有一个"files is undefined
" 错误,那么就是你没有选择正确的HTML元素,忘记了一个jQuery选择器返回的是匹配的DOM元素的列表。用获取的DOM元素调用“files”的方法就可以了。
在 change 事件发生时读取所选择的文件
另外,还可以在input
元素上的change
事件触发时再访问它的FileList
属性(但不是强制性的):
<input type="file" id="input" onchange="handleFiles(this.files)">
当用户成功选取若干个文件后,handleFiles()
函数会被调用,且一个代表用户所选择的文件的包含了File
对象的FileList
对象会作为参数传入该函数。
如果你的程序可以让用户选择多个文件,记得要在input元素上加上
multiple
属性:
<input type="file" id="input" multiple onchange="handleFiles(this.files)">
在用户选择了多个文件的情况下,传入handleFiles()
函数的文件列表将会包含多个File
对象,每个File
对象对应一个真实的文件。
动态添加change事件监听器
你还可以通过element.addEventListener()
方法来添加多个change
事件处理函数,像这样:
var inputElement = document.getElementById("inputField"); inputElement.addEventListener("change", handleFiles, false); function handleFiles() { var fileList = this.files; }
获取所选文件的信息
用户所选择的文件都存储在了一个FileList
对象上,其中每个文件都对应了一个File
对象。你可以通过这个FileList
对象的length
属性知道用户一共选择了多少个文件:
var numFiles = files.length;
可以通过普通的循环语句来操作每个单独的File
对象:
for (var i = 0, numFiles = files.length; i < numFiles; i++) { var file = files[i]; .. }
File
对象上有三个属性提供了所包含文件的相关信息.
name
- 文件名,只读字符串,不包含任何路径信息.
size
- 文件大小,单位为字节,只读的64位整数.
type
- MIME类型,只读字符串,如果类型未知,则返回空字符串.
显示文件大小
下面的例子演示了size
属性的用法:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>File(s) size</title> <script> function updateSize() { var nBytes = 0, oFiles = document.getElementById("uploadInput").files, nFiles = oFiles.length; for (var nFileId = 0; nFileId < nFiles; nFileId++) { nBytes += oFiles[nFileId].size; } var sOutput = nBytes + " bytes"; var aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"], nMultiple = 0, nApprox = nBytes / 1024; // optional code for multiples approximation for ( ; 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"> <p> <input id="uploadInput" type="file" name="myFiles" onchange="updateSize();" multiple> selected files: <span id="fileNum">0</span>; total size: <span id="fileSize">0</span> </p> <p> <input type="submit" value="Send file"> </p> </form> </body> </html>
在隐藏的文件输入框上调用click()方法
从Gecko 2.0 (Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1)开始,你可以隐藏掉默认的的
文件输入框<input>
元素,使用自定义的界面来充当打开文件选择对话框的按钮。实现起来很简单,你只需要使用样式display:none把原本的
文件输入框隐藏掉,然后在需要的时候调用它的click()
方法就行了。
考虑下面的HTML:
<input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)"> <a href="#" id="fileSelect">Select some files</a>
处理 click
事件的代码如下:
var fileSelect = document.getElementById("fileSelect"), fileElem = document.getElementById("fileElem"); fileSelect.addEventListener("click", function (e) { if (fileElem) { fileElem.click(); } e.preventDefault(); // prevent navigation to "#" }, false);
这样,你就能任意改变这个文件选择按钮的样式了。
通过拖放操作选择文件
你可以让用户将本地文件拖放到你的应用程序上.
首先要创建一个拖放操作的目的区域。可根据您的应用程序的设计来决定哪部分的内容接受 drop,但创建一个接收drop事件的元素是简单的:
var dropbox; dropbox = document.getElementById("dropbox"); dropbox.addEventListener("dragenter", dragenter, false); dropbox.addEventListener("dragover", dragover, false); dropbox.addEventListener("drop", drop, false);
在这个例子中,ID 为 dropbox
的元素所在的区域是我们的拖放目的区域。我们需要在该元素上绑定 dragenter,
dragover,
和drop 事件。
我们必须阻止dragenter
和dragover
事件的默认行为,这样才能触发 drop
事件:
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
对象,把该对象包含的Filelist
对象传入函数handleFiles(),
这个函数会无区别的对待从input元素或拖放操作中来的文件
列表。
例子:显示用户所选图片的缩略图
假设你正在开发下一个伟大的照片分享网站,并希望使用HTML5在用户上传他们图片之前进行缩略图预览。您可以如前面所讨论的建立一个输入元素或者拖放区域,并调用一个函数,如下面的 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" 是将要展示图片的 div preview.appendChild(img); var reader = new FileReader(); reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img); reader.readAsDataURL(file); } }
这里我们循环处理用户选择的文件,查看每个文件的类型属性,看看这是否是一个图像文件(通过一个正则表达式匹配字符串“image.*”)。对于每个图片文件,我们创建一个新的img元素。CSS可以用于建立任何漂亮的边界,阴影,和指定图像的大小,所以,甚至不需要在这里完成。
每张图片我们添加一个obj类,让他们更容易的在DOM树中被找到。我们也在图片上添加了一个file属性来确认每张图片的 File
,这样可以帮助我们在之后真正的上传工作时获取到图片。最后我们使用 Node.appendChild()
把缩略图添加到我们先前的文档区域中。
然后,我们建立了{ { domxref(FileReader)} }来处理图片的异步加载,并把它添加到img元素上。在创建新的FileReader对象之后,我们建立了onload函数,然后调用readAsDataURL()开始在后台进行读取操作。当图像文件的所有内容加载后,他们转换成一个 data: URL,传递到onload回调函数中。之后只需要把img元素的src属性设置为这个加载过的图像,就可以让图像的缩略图出现在用户的屏幕上。
使用对象URL
Gecko 2.0 (Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1)开始支持window.URL.createObjectURL()
和 window.URL.revokeObjectURL()
两个DOM方法。这两个方法创建简单的URL字符串对象,用于指向任何 DOM File
对象数据,包括用户电脑中的本地文件。
当你想要在HTML中通过URL来引用File
对象,你可以参考如下方式创建:
var objectURL = window.URL.createObjectURL(fileObj);
URL对象是 File
对象的一个字符串标识。 每次调用window.URL.createObjectURL()
的时候,会创建一个唯一的URL对象,即使你已经为该文件创建了URL对象。这些对象都必须被释放。 当文档被卸载时,它们会自动释放,如果你的页面动态地使用它们,你应该明确地通过调用window.URL.revokeObjectURL()
释放它们:
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>
这建立文件的<input>
元素就像一个调用文件选择器的链接(因为我们要隐藏文件输入,以防止表现出不友好的UI)。这部分将在 Using hidden file input elements using the click() method里解释,因为是调用文件选择器的方法。
下面是 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 { var list = document.createElement("ul"); 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(e) { 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); } fileList.appendChild(list); } }
通过 "fileList" ID
获取 <div>
。我们将向这个 div 块插入文件列表和略缩图的地方。
假如传递给 handleFiles() 函数的
FileList
对象为 null,那么就设置 div 块的 inner HTML 为 "No files selected!"。否则,我们将按下面的步骤建立文件列表:
- 创建一个无序列表 (
<ul>
) 元素。 - 通过调用
element.appendChild()
方法向<div>
块插入新的列表项。 files 即是
FileList
,对于其中的每一个File
:- 创建一个
<li>
元素,并将其插入 list 中。 - 创建一个
<img>
元素. - 使用
window.URL.createObjectURL()
创建URL,并设置图片的源为表示文件的新的 URL 对象。 - 设置图片的 height 为 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); } }
代码第二行创建了一个 imgs 数组,其包含了 document 中有 CSS 类 obj 的所有元素。在本例中,这些都是略缩图。
一旦我们有了这个列表,遍历该列表并为每一个元素创建一个 FileUpload 实例。
每个函数都会上传相应的文件。
实现文件上传
FileUpload
函数接受两个参数:一个 img 元素,一个可以从中读取到图像数据的文件 file。
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.sendAsBinary(evt.target.result); }; reader.readAsBinaryString(file); }
上面显示的 FileUpload()
函数创建了一个活动指示器(throbber),其是用于显示相关的进度信息,然后又创建了一个 XMLHttpRequest
去处理上传到服务器的数据。
在实际传输数据之前,需要完成几个步骤:
- 设置
XMLHttpRequest
的 uploadprogress
监听器以新的百分比信息去更新活动指示器(throbber),并将其作为上传进度,而活动指示器将基于最新的信息进行更新。 - 设置
XMLHttpRequest
的 uploadload
事件句柄更新信息活动指示器的进度直到其为100%(避免过程中的 granularity quirks)。上传完成后活动指示器就会消失。 - 通过调用的
XMLHttpRequest
的open()
方法生成一个POST
请求以其启动图像文件上传。 - MIME 类型上传是通过调用
XMLHttpRequest
的函数overrideMimeType()
设置。在这种情况下,我们使用一个通用的 MIME 类型;你可以选择不设置 MIME 类型,这取决于你的使用情况。 -
FileReader
对象可以把 file 转换成二进制字符串。 - 最后,当内容载入完毕的时候就会调用
XMLHttpRequest
的sendAsBinary()
函数去上传 file 的内容。
异步实现文件上传
<?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="text/javascript"> function sendFile(file) { var uri = "/index.php"; var xhr = new XMLHttpRequest(); var fd = new FormData(); xhr.open("POST", uri, true); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { // Handle response. alert(xhr.responseText); // handle response. } }; fd.append('myFile', file); // Initiate a multipart/form-data upload xhr.send(fd); } window.onload = function() { var dropzone = document.getElementById("dropzone"); dropzone.ondragover = dropzone.ondragenter = function(event) { event.stopPropagation(); event.preventDefault(); } dropzone.ondrop = function(event) { event.stopPropagation(); event.preventDefault(); var filesArray = event.dataTransfer.files; for (var 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>
示例: 使用URLs对象显示 PDF
URLs对象不仅仅是可用于图像!它们可用于显示嵌入的PDF文件,或可以由浏览器显示的任何其它资源。
在火狐中,使PDF出现在内嵌的iframe中(并不建议作为一个下载的文件),把偏好pdfjs.disabled设置为false 。
<iframe id="viewer">
src
属性在这里有些变化:
var obj_url = window.URL.createObjectURL(blob); var iframe = document.getElementById('viewer'); iframe.setAttribute('src', obj_url); window.URL.revokeObjectURL(obj_url);
示例:其他文件类型使用URLs对象
你可以用同样的方式操纵其它格式的文件。下面是如何预览上传的视频:
var video = document.getElementById('video'); var obj_url = window.URL.createObjectURL(blob); video.src = obj_url; video.play() window.URL.revokeObjectURL(obj_url);
规范
- File upload state (HTML 5工作草案)
- File API
相关链接
File
FileList
FileReader
- Using XMLHttpRequest
- Using the DOM File API in chrome code
XMLHttpRequest
- jQuery JavaScript library