本主题介绍如何使用文件 API 读取本地文件。

 

简介

Internet Explorer 10(及更高版本)能够以安全的方式从本地文件系统(客户端计算机)中读取文件,而无需扩展或插件。本主题涵盖以下与核心文件有关的任务:

  • 使用 input 元素以及拖放 (DnD) 框选择本地文件。
  • 读取所选文件、显示其内容以及显示文件元数据,如大小、文件类型、创建日期等。

在图面上,访问本地文件系统似乎存在很大的安全漏洞。但是,其实包含了很多安全措施;尤其是只能访问文件(如果用户提供这样做的权限)。

注意  以下代码示例需要使用支持文件 API 的浏览器,如 Internet Explorer 10 或更高版本。

选择文件

用户有两种常见的方法来选择本地文件。最简单的方法是使用 input 元素。稍微复杂点的另一种方法是创建 DnD 框。

使用 input 元素选择文件

通过在 input 元素上使用 type="file",选择文件变得相对简单:

 
<input type="file" id="fileSelector" multiple accept="image/*">

单击所得的“浏览”按钮时,multiple 特性允许用户在随后的文件选择对话框中选择多个文件。accept 特性仅允许用户选择图形文件,如 .jpg、.gif、.bmp 等。 以下简单示例演示如何使用 input 元素选择多个图形文件:

示例 1

 
<!DOCTYPE html>
<html>
  <head>
    <title>&lt;input&gt; File Selection</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">
  </head>
  <body>
    <h1>HTML5 &lt;input&gt; File Selection</h1>
    <h3>Example 1</h3>
    <input type="file" id="fileSelector" multiple accept="image/*" /> <!-- By design, if you select the exact same files two or more times, the 'change' event will not fire. -->
    <ul id="fileContentList" style="list-style-type: none;"></ul> <!-- This will be populated with <li> elements via JavaScript. -->
    <script type="text/javascript">
      var message = [];

      if (!document.getElementById('fileSelector').files) {
        message = '<p>The ' +
                  '<a href="http://dev.w3.org/2006/webapi/FileAPI/" target="_blank">File API</a>s ' +
                  'are not fully supported by this browser.</p>' +
                  '<p>Upgrade your browser to the latest version.</p>';
      
        document.querySelector('body').innerHTML = message;
      }
      else {
        document.getElementById('fileSelector').addEventListener('change', handleFileSelection, false); // Add an onchange event listener for the <input id="fileSelector"> element.
      }
      
      function handleFileSelection(evt) {    
        var files = evt.target.files; // The files selected by the user (as a FileList object).
  
        if (!files) {
          msa.alert("<p>At least one selected file is invalid - do not select any folders.</p><p>Please reselect and try again.</p>");
          return;
        }

        // The variable "files" is an array of file objects. 
        for (var i = 0, file; file = files[i]; i++) {
          var img_element = document.createElement('img'); // We've only allowed the user to select graphics files, so get ready to display them.
          img_element.src = window.URL.createObjectURL(file); // Assumes "file" is some sort of graphics file type.
          img_element.width = 150; // Make all images the same width.
          img_element.style.verticalAlign = "middle"; // Center the image in the middle of adjacent text.
          img_element.style.margin = "4px 4px 4px 0";
          img_element.onload = function() { window.URL.revokeObjectURL(this.src); } // The file URL is not needed once the file image has been fully loaded.
          
          var span_element = document.createElement('span');
          span_element.innerHTML = file.name;
          
          var li_element = document.createElement('li');
          li_element.appendChild(img_element);
          li_element.appendChild(span_element);
          
          document.getElementById('fileContentList').appendChild(li_element);
        } // for
      } // handleFileSelection
    </script>  
    <script src="../utilities.js" type="text/javascript"></script> <!-- Provides the msa.alert() method. -->
  </body>
</html>

在上面,<meta http-equiv="X-UA-Compatible" content="IE=10"> 指示 Windows Internet Explorer 采用 IE10 模式播放页面。有关详细信息,请参阅定义文档兼容性

通常会将 script 块放置在 body 块的末尾,目的是提高性能,但更重要的是允许访问以前呈现的 DOM 元素,如 <input type="file" id="fileSelector" multiple accept="image/*" />

示例 1 的算法非常简单:

  1. 如果未提供特定的文件 API 功能,则将所有之前的标记都替换为警告用户所需的文件 API 功能不可用的 HTML。否则,添加一个事件侦听器 (handleFileSelection) 以侦听 input 元素上的任何更改。

  2. 当用户选择一个或多个文件时(通过 input 元素),引发 change 事件,该事件调用 handleFileSelection 函数。传递给 handleFileSelection 的事件对象包含用户选择的文件列表,即 evt.target.files。随后,对于文件列表中的每个文件对象,我们将创建图像缩略图列表并显示关联的文件名。

注意  由于 alert 不能在使用 JavaScript 的 Windows 应用商店应用中使用,因此我们转而使用 msa.alert。如果你不是为使用 JavaScript 的 Windows 应用商店应用进行开发,则可以将 msa.alert 替换为 JavaScript 的传统 alert,且功能不会有任何损失。

使用拖放框选择文件

之前用于选择文件的 input 元素技术相对来说比较简单,但可能看上去不太美观。更复杂的方法是创建文件 DnD 框,如下一示例中所示:

示例 2

 
<!DOCTYPE html>
<html>
  <head>
    <title>Drag &amp; Drop File Selection</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">
    <style>
      #fileDropBox {
        width: 20em;
        line-height: 10em;
        border: 1px dashed gray;
        text-align: center;
        color: gray;
        border-radius: 7px;
    </style>
  </head>
  <body>
    <h1>HTML5 Drag and Drop File Selection</h1>
    <h3>Example 2</h3>
    <p>Using Windows Explorer (or similar), select one or more files (directories are not allowed), and then drag them to the below drop box:</p>
    <div id="fileDropBox">Drop files here.</div>
    <ul id="list"></ul>
    <script>
      var message = [];

      if (!window.FileReader) {
        message = '<p>The ' +
                  '<a href="http://dev.w3.org/2006/webapi/FileAPI/" target="_blank">File API</a>s ' +
                  'are not fully supported by this browser.</p>' +
                  '<p>Upgrade your browser to the latest version.</p>';
      
        document.querySelector('body').innerHTML = message;
      }
      else {
        // Set up the file drag and drop listeners:         
        document.getElementById('fileDropBox').addEventListener('dragover', handleDragOver, false);
        document.getElementById('fileDropBox').addEventListener('drop', handleFileSelection, false);
      }

      function handleDragOver(evt) {
        evt.stopPropagation();  // Do not allow the dragover event to bubble.
        evt.preventDefault(); // Prevent default dragover event behavior.
      } // handleDragOver()

      function handleFileSelection(evt) {
        evt.stopPropagation(); // Do not allow the drop event to bubble.
        evt.preventDefault(); // Prevent default drop event behavior.

        var files = evt.dataTransfer.files; // Grab the list of files dragged to the drop box.

        if (!files) {
          msa.alert("<p>At least one selected file is invalid - do not select any folders.</p><p>Please reselect and try again.</p>");
          return;
        }

        // "files" is a FileList of file objects. List a few file object properties:    
        var output = [];
        for (var i = 0, f; i < files.length; i++) {
          try {
            f = files[i]; // If anything goes awry, the error would occur here.
            output.push('<li><strong>', 
                        f.name, '</strong> (',
                        f.type || 'unknown file type', 
                        ') - ',
                        f.size, ' bytes, last modified: ',
                        f.lastModifiedDate, 
                        '</li>');
            document.getElementById('list').innerHTML = output.join('');
          } // try
          catch (fileError) {
            msa.alert("<p>An unspecified file error occurred.</p><p>Selecting one or more folders will cause a file error.</p>");
            console.log("The following error occurred at i = " + i + ": " + fileError); // Send the error object to the browser's debugger console window, if active.
            return;
          } // catch
        } // for
      } // handleFileSelection()      
    </script>    
    <script src="../utilities.js" type="text/javascript"></script> <!-- Provides the msa.alert() method. -->        
  </body>
</html>

文件 DnD 框只是一个 CSS 风格的 div 元素(该元素的 ID 为 fileDropBox),并有两个附加的事件侦听器。第一个事件侦听器 handleDragOver 用于将 dragover 事件置空。第二个 handleFileSelection 用于显示文件元数据信息(文件名、大小等)

调用 handleFileSelection 函数时,它将传递事件对象 (evt),该对象包含用户所选的文件列表:

 
var files = evt.dataTransfer.files;

随后,针对文件列表中的每个文件,我们将构建一个文件元数据信息的分类项目符号列表(推入 output 数组)。然后使用 <ul id=”list”> 元素通过以下方式显示这个项目符号列表:

 
document.getElementById('list').innerHTML = output.join('');

读取文件

现在我们可以选择文件了,那么下一步就是显示所选文本文件的内容。以下示例将完成此类操作。

示例 3

 
<!DOCTYPE html>
<html>
  <head>
    <title>Drag &amp; Drop File Selection</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">    
    <style>
      #fileDropBox {
        width: 20em;
        line-height: 10em;
        border: 1px dashed gray;
        text-align: center;
        color: gray;
        border-radius: 7px;
    </style>
  </head>
  <body>
    <h1>HTML5 Drag and Drop File Selection</h1>
    <h3>Example 3</h3>
    <p>Using Windows Explorer (or similar), select one or more text files (directories are not allowed), and then drag them to the below drop box:</p>
    <div id="fileDropBox">Drop files here.</div>
    <script>
      var message = [];

      if (!window.FileReader) {
        message = '<p>The ' +
                  '<a href="http://dev.w3.org/2006/webapi/FileAPI/" target="_blank">File API</a>s ' +
                  'are not fully supported by this browser.</p>' +
                  '<p>Upgrade your browser to the latest version.</p>';
      
        document.querySelector('body').innerHTML = message;
      }
      else {
        // Set up the file drag and drop listeners:         
        document.getElementById('fileDropBox').addEventListener('dragover', handleDragOver, false);
        document.getElementById('fileDropBox').addEventListener('drop', handleFileSelection, false);
      }
      
      function sanitizeHTML(htmlString) {
        var tmp = document.createElement('div');
        tmp.appendChild( document.createTextNode(htmlString) );
        return tmp.innerHTML;
      } // stripHtmlFromText

      function handleDragOver(evt) {
        evt.stopPropagation();  // Do not allow the dragover event to bubble.
        evt.preventDefault(); // Prevent default dragover event behavior.
      } // handleDragOver

      function displayFileText(evt) {
        var fileString = evt.target.result; // Obtain the file contents, which was read into memory.

        //evt.target is a FileReader object, not a File object; so window.URL.createObject(evt.target) won't work here!
        msa.alert("<pre>" + sanitizeHTML(fileString) + "</pre>", {width: 40, tile: true});  // sanitizeHTML() is used in case the user selects one or more HTML or HTML-like files and the <pre> tag preserves both spaces and line breaks.
      } // displayFileText

      function handleFileReadAbort(evt) {
        msa.alert("File read aborted.");
      } // handleFileReadAbort

      function handleFileReadError(evt) {
        var message;
        
        switch (evt.target.error.name) {
          case "NotFoundError":
            msa.alert("The file could not be found at the time the read was processed.");
            break;
          case "SecurityError":
            message = "<p>A file security error occured. This can be due to:</p>";
            message += "<ul><li>Accessing certain files deemed unsafe for Web applications.</li>";
            message += "<li>Performing too many read calls on file resources.</li>";
            message += "<li>The file has changed on disk since the user selected it.</li></ul>";
            msa.alert(message);
            break;
          case "NotReadableError":
            msa.alert("The file cannot be read. This can occur if the file is open in another application.");
            break;
          case "EncodingError":
            msa.alert("The length of the data URL for the file is too long.");
            break;
          default:
            msa.alert("File error code " + evt.target.error.name);
        } // switch
      } // handleFileReadError

      function startFileRead(fileObject) {
        var reader = new FileReader();

        // Set up asynchronous handlers for file-read-success, file-read-abort, and file-read-errors:
        reader.onloadend = displayFileText; // "onloadend" fires when the file contents have been successfully loaded into memory.
        reader.abort = handleFileReadAbort; // "abort" files on abort.
        reader.onerror = handleFileReadError; // "onerror" fires if something goes awry.

        if (fileObject) { // Safety first.
          reader.readAsText(fileObject); // Asynchronously start a file read thread. Other supported read methods include readAsArrayBuffer() and readAsDataURL().
        }
      } // startFileRead

      function handleFileSelection(evt) {
        evt.stopPropagation(); // Do not allow the drop event to bubble.
        evt.preventDefault(); // Prevent default drop event behavior.

        var files = evt.dataTransfer.files; // Grab the list of files dragged to the drop box.

        if (!files) {
          msa.alert("<p>At least one selected file is invalid - do not select any folders.</p><p>Please reselect and try again.</p>");
          return;
        }

        // "files" is a FileList of file objects. Try to display the contents of each file:
        for (var i = 0, file; file = files[i]; i++) {
          if (!file) {
            msa.alert("Unable to access " + file.name); 
            continue; // Immediately move to the next file object.
          }
          if (file.size == 0) {
            msa.alert("Skipping " + file.name.toUpperCase() + " because it is empty.");
            continue;
          }
          if ( !file.type.match('text/.*') ) {
            msa.alert("Skipping " + file.name.toUpperCase() + " because it is not a known text file type.");
            continue;
          }
          startFileRead(file); // Asychronously fire off a file read request.
        } // for
      } // handleFileSelection      
    </script>
    <script src="utilities.js" type="text/javascript"></script> <!-- Provides the msa.alert() method. -->    
  </body>
</html>

由于示例 3 基于示例 2,因此算法也类似:

  1. 如果未提供特定的文件 API 功能,则将所有之前的标记都替换为警告用户所需的文件 API 功能不可用的 HTML。否则,向 DnD 框中添加两个 DnD 事件侦听器:

     
    <div id="fileDropBox">Drop files here.</div>
    
    
  2. 当用户将文件从 Windows Explorer 窗口(或类似窗口)拖动到 DnD 框时,引发 drop 事件并执行 handleFileSelection 函数。传递给 handleFileSelection 的事件 (evt) 包含用户选择的文件。

  3. 然后,handleFileSelection 循环它处理的每个文件(通过 evt.dataTransfer.files),进行一些基本错误检查,并通过 startFileRead(file) 针对给定文件发出异步读取请求。当文件成功读入内存时,调用事件处理程序以对文件数据进行有用的操作,如下所述。

  4. startFileRead(fileObject) 函数为传递给它的每个文件创建一个新的 FileReader 对象。每个文件 FileReader 对象(例如代码中的 reader)显示用于包含函数指针(即,事件处理程序)的属性。reader 还显示很多文件读取方法,尤其是在 reader.readAsText 中的方法。当 reasAsText 方法成功(并异步)将文件的内容加载到内存中时,调用 reader.onloadend 中包含的函数指针。即,执行 displayFileText 事件处理程序。

  5. 传递给 displayFileText 的事件对象包含读取的结果(由 reader.reasAsText 策划)。更确切说,evt.target.result 包含针对关联文本文件请求读取的字符串格式的结果。然后,按照如下方式显示此字符串(即,文件的文本内容):

     
    msa.alert("<pre>" + sanitizeHTML(fileString) + "</pre>", {width: 40, tile: true});
    
    

    如果用户选择一个或多个 HTML 或类似 HTML 的文件,则调用 santizeHTML 以将“<" with "&lt;", ">”替换为“&gt;”等。使用 <pre> 标记保留文件内容的固有空格和换行。 如上所述,标准的 alert 方法与 msa.alert 的工作方式一样,但在使用 JavaScript 的 Windows 应用商店应用中禁止使用。msa.alert 的第二个参数是向此方法传递可选参数的一个对象文本值。尤其是 tile: true 允许平铺多个警告框(即,轻微偏移)并且 width: 40 生成 40em 单位宽度的警告框。

  6. 返回到 startFileRead 函数,如果发生文件读取错误或者用户取消读取操作,则会分别调用 handleFileReadErrorhandleFileReadAbort 事件处理程序。

现在,你应该能够使用 HTML5 和 JavaScript 读取本地文件(及其内容)了。也可尝试使用其他 FileReader 读取方法。尤其是,尝试使用 readAsArrayBuffer 方法编写自己的应用程序。