在web应用中使用文件

使用HTML5 DOM新增的File API,现在可以让网页要求用户选择本地文件,并且读取这些文件的信息了。选择的方式既可以是HTML<input> 元素,也可以是拖拽 。

你可以在chrome扩展等代码中使用DOM File API ;事实上有些需要注意的额外特性。参考 Using the DOM File API in chrome code 。

访问选中的文件Edit

考虑下面的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事件发生时读取所选择的文件Edit

另外,还可以在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; 
}

获取所选文件的信息Edit

用户所选择的文件都存储在了一个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()方法Edit

从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);

这样,你就能任意改变这个新按钮的样式了.

通过拖放操作选择文件Edit

你可以让用户将本地文件拖放到你的应用程序上.

首先要创建一个拖放操作的目的区域:

第一步是建立一个放置区域。可根据您的应用程序的设计来决定哪部分的内容接受 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元素或拖放操作中来的文件列表。

例子:显示用户所选图片的缩略图Edit

假设你正在开发下一个伟大的照片分享网站,并希望使用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;
    // Assuming that "preview" is the div output where the content will be displayed
    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属性设置为这个加载过的图像,就可以让图像的缩略图出现在用户的屏幕 上。

使用对象URLEdit

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来显示图片Edit

这个例子使用了对象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);
  }
}

This starts by fetching the URL of the <div> with the ID fileList. This is the block into which we'll insert out file list, including thumbmails.

If the FileList object passed to handleFiles() is null, we simply set the inner HTML of the block to display "No files selected!". Otherwise, we start building our file list, as follows:

  1. A new unordered list (<ul>) element is created.
  2. The new list element is inserted into the <div> block by calling its element.appendChild() method.
  3. For each File in the FileList represented by files
    1. Create a new list item (<li>) element and insert it into the list.
    2. Create a new image (<img>) element.
    3. Set the image's source to a new object URL representing the file, using window.URL.createObjectURL() to create the blob URL.
    4. Set the image's height to 60 pixels.
    5. Set up the image's load event handler to release the object URL, since it's no longer needed once the image has been loaded. This is done by calling the window.URL.revokeObjectURL() method, passing in the object URL string as specified by img.src.
    6. Append the new list item to the list.

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

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

创建上传任务

Continuing with the code that builds the thumbnails in the previous example, recall that every thumbnail image is in the CSS class obj, with the corresponding File attached in a file attribute. This lets us very easily select all the images the user has chosen for uploading using Document.querySelectorAll(), like this:

function sendFiles() {
  var imgs = document.querySelectorAll(".obj");
  
  for (var i = 0; i < imgs.length; i++) {
    new FileUpload(imgs[i], imgs[i].file);
  }
}

Line 2 creates an array, called imgs, of all the elements in the document with the CSS class obj. In our case, these will be all the image thumbnails. Once we have that list, it's trivial to go through the list, creating a new FileUpload instance for each. Each of these handles uploading the corresponding file.

实现文件上传

FileUpload函数接受两个参数:一个IMG元素,一个File对象或Blob对象.

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);
}

The FileUpload() function shown above creates a throbber, which is used to display progress information, then creates an XMLHttpRequest to handle uploading the data.

Before actually transferring the data, several preparatory steps are taken:

  1. The XMLHttpRequest's upload progress listener is set to update the throbber with new percentage information, so that as the upload progresses, the throbber will be updated based on the latest information.
  2. The XMLHttpRequest's upload load event handler is set to update the throbber with 100% as the progress information (to ensure the progress indicator actually reaches 100%, in case of granularity quirks during the process). It then removes the throbber, since it's no longer needed. This causes the throbber to disappear once the upload is complete.
  3. The request to upload the image file is opened by calling XMLHttpRequest's open() method to start generating a POST request.
  4. The MIME type for the upload is set by calling the XMLHttpRequest function overrideMimeType(). In this case, we're using a generic MIME type; you may or may not need to set the MIME type at all, depending on your use case.
  5. The FileReader object is used to convert the file to a binary string.
  6. Finally, when the content is loaded the XMLHttpRequest function sendAsBinary() is called to upload the file's content.

异步实现文件上传

<?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对象显示 PDFEdit

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对象Edit

你可以用同样的方式操纵其它格式的文件。下面是如何预览上传的视频:

var video = document.getElementById('video');
var obj_url = window.URL.createObjectURL(blob);
video.src = obj_url;
video.play()
window.URL.revokeObjectURL(obj_url);
posted on 2015-12-02 20:33  張暁磊  阅读(629)  评论(0编辑  收藏  举报