在web应用中使用文件

使用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 的元素所在的区域是我们的拖放目的区域。我们需要在该元素上绑定 dragenterdragover,drop 事件。

我们必须阻止dragenterdragover事件的默认行为,这样才能触发 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!"。否则,我们将按下面的步骤建立文件列表:

  1. 创建一个无序列表 (<ul>) 元素。
  2. 通过调用 element.appendChild() 方法向 <div> 块插入新的列表项。
  3. files 即是 FileList,对于其中的每一个 File
    1. 创建一个 <li> 元素,并将其插入 list 中。
    2. 创建一个 <img> 元素.
    3. 使用 window.URL.createObjectURL() 创建URL,并设置图片的源为表示文件的新的 URL 对象。
    4. 设置图片的 height 为 60 像素。
    5. 创建图片的 load 事件句柄以其解除 URL 对象,因为图像载入之后就不再需要 URL。这是通过调用 window.URL.revokeObjectURL() 方法来完成的,用 img.src 传入指定的 URL 对象字符串。
    6. 添加新的列表项到列表之中。

例子:上传用户选择的文件

你可以异步的将用户所选择的文件上传到服务器上(比如一张图片).

创建上传任务

紧接上面构建缩略图例子中的代码,需要重申的是 每一个略缩图都在 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  去处理上传到服务器的数据。

在实际传输数据之前,需要完成几个步骤:

  1. 设置 XMLHttpRequest 的 upload progress 监听器以新的百分比信息去更新活动指示器(throbber),并将其作为上传进度,而活动指示器将基于最新的信息进行更新。
  2. 设置 XMLHttpRequest 的 upload load 事件句柄更新信息活动指示器的进度直到其为100%(避免过程中的 granularity quirks)。上传完成后活动指示器就会消失。
  3. 通过调用的 XMLHttpRequestopen() 方法生成一个 POST 请求以其启动图像文件上传。
  4. MIME 类型上传是通过调用 XMLHttpRequest 的函数 overrideMimeType() 设置。在这种情况下,我们使用一个通用的 MIME 类型;你可以选择不设置 MIME 类型,这取决于你的使用情况。
  5.  FileReader 对象可以把 file 转换成二进制字符串。
  6. 最后,当内容载入完毕的时候就会调用 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);

规范

相关链接

文档标签和贡献者

 此页面的贡献者: Ende93, fuchao2012, Leo_Yao, jtyjty99999, ziyunfei
 最后编辑者: Ende93,