参考: https://developer.mozilla.org/zh-CN/docs/Using_files_from_web_applications
使用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];
在 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
对象上有三个属性提供了所包含文件的相关信息.
- 文件名,只读字符串,不包含任何路径信息.
- 文件大小,单位为字节,只读的64位整数.
- MIME类型,只读字符串,如果类型未知,则返回空字符串.
name
size
type
显示文件大小
下面的例子演示了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()
把缩略图添加到我们先前的文档区域中。
然后,我们建立了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);