Lesen von Dateien in JavaScript mit den File APIs

HTML5 Rocks

Einführung

HTML5 bietet über die File API-Spezifikation endlich eine Standardmethode für die Interaktion mit lokalen Dateien. Mit dem File API können Sie beispielsweise eine Miniaturansicht von Bildern in der Vorschau anzeigen, während diese an den Server gesendet werden, oder es einer App ermöglichen, einen Dateiverweis zu speichern, während der Nutzer offline ist. Außerdem können Sie mithilfe der clientseitigen Logik überprüfen, ob der Mimetyp eines Uploads mit seiner Dateiendung übereinstimmt, oder die Größe von Uploads beschränken.

Die Spezifikation bietet verschiedene Schnittstellen zum Zugriff auf Dateien aus einem lokalen Dateisystem:

  1. File - eine einzelne Datei. Stellt schreibgeschützte Informationen wie Name, Dateigröße, Mimetyp und einen Verweis auf den Dateihandle bereit.
  2. FileList - eine arrayartige Folge von File-Objekten. (Denken Sie an <input type="file" multiple> oder das Ziehen eines Dateiverzeichnisses vom Desktop.)
  3. Blob - ermöglicht das Unterteilen einer Datei in Bytebereiche.

Bei Verwendung in Verbindung mit den obigen Datenstrukturen kann die FileReader-Schnittstelle dazu genutzt werden, eine Datei über die vertraute JavaScript-Ereignisbehandlung asynchron zu lesen. Dadurch können Sie den Fortschritt eines Lesevorgangs überwachen, Fehler ermitteln und feststellen, wann ein Ladevorgang abgeschlossen ist. Die APIs ähneln in vielerlei Hinsicht dem XMLHttpRequest-Ereignismodell.

Hinweis: Zum Zeitpunkt der Erstellung dieser Anleitung werden die erforderlichen APIs zur Arbeit mit lokalen Dateien in Chrome 6.0 und Firefox 3.6 unterstützt. Ab Firefox 3.6.3 wird die File.slice()-Methode nicht unterstützt.

Auswählen von Dateien

Überprüfen Sie zunächst, ob Ihr Browser das File API vollständig unterstützt:

// Check for the various File API support.
if (window.File && window.FileReader && window.FileList && window.Blob) {
  // Great success! All the File APIs are supported.
} else {
  alert('The File APIs are not fully supported in this browser.');
}

Falls Ihre App nur einige dieser APIs verwendet, passen Sie dieses Snippet einfach entsprechend an.

Verwenden der Formulareingabe für die Auswahl

Die einfachste Methode zum Laden einer Datei ist die Verwendung eines Standardelements vom Typ <input type="file">. JavaScript gibt die Liste der ausgewählten File-Objekte als FileList zurück. Im folgenden Beispiel wird das "multiple"-Attribut verwendet, um die Auswahl mehrerer Dateien gleichzeitig zu ermöglichen:

<input type="file" id="files" name="files[]" multiple />
<output id="list"></output>

<script>
  function handleFileSelect(evt) {
    var files = evt.target.files; // FileList object

    // files is a FileList of File objects. List some properties.
    var output = [];
    for (var i = 0, f; f = files[i]; i++) {
      output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate.toLocaleDateString(), '</li>');
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>

Beispiel: Verwenden der Formulareingabe für die Auswahl. Probieren Sie es aus!

Verwenden von Drag & Drop für die Auswahl

Eine andere Technik zum Laden von Dateien ist das native Drag & Drop vom Desktop in den Browser. Wir können unser vorheriges Beispiel leicht abändern, um den Drag & Drop-Support zu integrieren.

<div id="drop_zone">Drop files here</div>
<output id="list"></output>

<script>
  function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();

    var files = evt.dataTransfer.files; // FileList object.

    // files is a FileList of File objects. List some properties.
    var output = [];
    for (var i = 0, f; f = files[i]; i++) {
      output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate.toLocaleDateString(), '</li>');
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
  }

  // Setup the dnd listeners.
  var dropZone = document.getElementById('drop_zone');
  dropZone.addEventListener('dragover', handleDragOver, false);
  dropZone.addEventListener('drop', handleFileSelect, false);
</script>

Beispiel: Verwenden von Drag & Drop für die Auswahl. Probieren Sie es aus!

Dateien hier ablegen

Hinweis: Einige Browser behandeln <input type="file">-Elemente als native Drop-Ziele. Versuchen Sie, Dateien auf das Eingabefeld im vorherigen Beispiel zu ziehen.

Lesen von Dateien

Jetzt wird's interessant!

Nachdem Sie einen File-Verweis abgerufen haben, instanziieren Sie ein FileReader-Objekt, um den Inhalt in den Speicher zu lesen. Sobald der Ladevorgang abgeschlossen ist, wird das onload-Ereignis des Readers ausgelöst und das result-Attribut kann für den Zugriff auf die Dateidaten verwendet werden.

FileReader bietet vier Optionen zum asynchronen Lesen einer Datei:

  • FileReader.readAsBinaryString(Blob|File) - die result-Eigenschaft enthält die Datei-/Blob-Daten als binären String. Jedes Byte wird als Ganzzahl im Bereich [0..255] dargestellt.
  • FileReader.readAsText(Blob|File, opt_encoding) - die result-Eigenschaft enthält die Datei-/Blob-Daten als Textstring. Der String wird standardmäßig mit "UTF-8" entschlüsselt. Mit dem optionalen Codierungsparameter können Sie ein anderes Format angeben.
  • FileReader.readAsDataURL(Blob|File) - die result-Eigenschaft enthält die Datei-/Blob-Daten verschlüsselt als Data-URL.
  • FileReader.readAsArrayBuffer(Blob|File) - die result-Eigenschaft enthält die Datei-/Blob-Daten als ArrayBuffer-Objekt.

Eine dieser Lesemethoden wird auf Ihrem FileReader-Objekt aufgerufen. Mit onloadstart, onprogress, onload, onabort, onerror und onloadend kann der Fortschritt verfolgt werden.

Im folgenden Beispiel werden Bilder aus der Auswahl des Nutzers herausgefiltert, reader.readAsDataURL() auf der Datei aufgerufen und eine Miniaturansicht durch Einstellen des "src"-Attributs auf eine Data-URL gerendert.

<style>
  .thumb {
    height: 75px;
    border: 1px solid #000;
    margin: 10px 5px 0 0;
  }
</style>

<input type="file" id="files" name="files[]" multiple />
<output id="list"></output>

<script>
  function handleFileSelect(evt) {
    var files = evt.target.files; // FileList object

    // Loop through the FileList and render image files as thumbnails.
    for (var i = 0, f; f = files[i]; i++) {

      // Only process image files.
      if (!f.type.match('image.*')) {
        continue;
      }

      var reader = new FileReader();

      // Closure to capture the file information.
      reader.onload = (function(theFile) {
        return function(e) {
          // Render thumbnail.
          var span = document.createElement('span');
          span.innerHTML = ['<img class="thumb" src="', e.target.result,
                            '" title="', escape(theFile.name), '"/>'].join('');
          document.getElementById('list').insertBefore(span, null);
        };
      })(f);

      // Read in the image file as a data URL.
      reader.readAsDataURL(f);
    }
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>

Beispiel: Lesen von Dateien. Probieren Sie es aus!

Testen Sie dieses Beispiel mit einem Bildverzeichnis!


Aufteilen einer Datei

Manchmal ist es nicht die beste Option, die gesamte Datei in den Speicher zu lesen. Angenommen, Sie möchten einen asynchronen Datei-Uploader erstellen. Eine Möglichkeit zur Beschleunigung des Uploads bestünde darin, die Datei in einzelnen Bytebereich-Stücken zu lesen und zu senden. Die Serverkomponente wäre dann dafür zuständig, den Dateiinhalt in der richtigen Reihenfolge wieder zusammenzusetzen.

Glücklicherweise unterstützt die File-Schnittstelle eine Aufteilungsmethode für diesen Anwendungsfall. Diese Methode verwendet ein Startbyte als erstes Argument, ein Endbyte als zweites und einen optionalen Inhaltstyp-String als drittes Argument. Die Semantik dieser Methode hat sich vor Kurzem geändert, darum wurde ein Anbieter-Präfix hinzugefügt:

if (file.webkitSlice) {
  var blob = file.webkitSlice(startingByte, endindByte);
} else if (file.mozSlice) {
  var blob = file.mozSlice(startingByte, endindByte);
}
reader.readAsBinaryString(blob);

Im folgenden Beispiel ist das Lesen von Dateistücken dargestellt. Erwähnenswert ist, dass in dem Beispiel das onloadend-Ereignis verwendet und der evt.target.readyState überprüft wird, statt das onload-Ereignis zu verwenden.

<style>
  #byte_content {
    margin: 5px 0;
    max-height: 100px;
    overflow-y: auto;
    overflow-x: hidden;
  }
  #byte_range { margin-top: 5px; }
</style>

<input type="file" id="files" name="file" /> Read bytes: 
<span class="readBytesButtons">
  <button data-startbyte="0" data-endbyte="4">1-5</button>
  <button data-startbyte="5" data-endbyte="14">6-15</button>
  <button data-startbyte="6" data-endbyte="7">7-8</button>
  <button>entire file</button>
</span>
<div id="byte_range"></div>
<div id="byte_content"></div>

<script>
  function readBlob(opt_startByte, opt_stopByte) {

    var files = document.getElementById('files').files;
    if (!files.length) {
      alert('Please select a file!');
      return;
    }

    var file = files[0];
    var start = parseInt(opt_startByte) || 0;
    var stop = parseInt(opt_stopByte) || file.size - 1;

    var reader = new FileReader();

    // If we use onloadend, we need to check the readyState.
    reader.onloadend = function(evt) {
      if (evt.target.readyState == FileReader.DONE) { // DONE == 2
        document.getElementById('byte_content').textContent = evt.target.result;
        document.getElementById('byte_range').textContent = 
            ['Read bytes: ', start + 1, ' - ', stop + 1,
             ' of ', file.size, ' byte file'].join('');
      }
    };

    if (file.webkitSlice) {
      var blob = file.webkitSlice(start, stop + 1);
    } else if (file.mozSlice) {
      var blob = file.mozSlice(start, stop + 1);
    }
    reader.readAsBinaryString(blob);
  }
  
  document.querySelector('.readBytesButtons').addEventListener('click', function(evt) {
    if (evt.target.tagName.toLowerCase() == 'button') {
      var startByte = evt.target.getAttribute('data-startbyte');
      var endByte = evt.target.getAttribute('data-endbyte');
      readBlob(startByte, endByte);
    }
  }, false);
</script>

Beispiel: Aufteilen einer Datei. Probieren Sie es aus!

Bytes lesen:

Überwachen des Lesefortschritts

Ein praktisches Extra, das wir beim Verwenden der asynchronen Ereignisbehandlung erhalten, ist die Möglichkeit der Überwachung des Lesefortschritts. Dies ist vor allem nützlich, um große Dateien zu lesen, Fehler zu erkennen und herauszufinden, wann der Lesevorgang abgeschlossen ist.

Der Lesefortschritt kann mithilfe des onloadstart- und des onprogress-Ereignisses kontrolliert werden.

Im folgenden Beispiel wird die Anzeige einer Statusleiste zur Überwachung eines Lesevorgangs dargestellt. Machen Sie einen Test mit einer großen Datei oder einer Datei von einem Remotelaufwerk, um die Statusleiste in Aktion zu sehen.

<style>
  #progress_bar {
    margin: 10px 0;
    padding: 3px;
    border: 1px solid #000;
    font-size: 14px;
    clear: both;
    opacity: 0;
    -moz-transition: opacity 1s linear;
    -o-transition: opacity 1s linear;
    -webkit-transition: opacity 1s linear;
  }
  #progress_bar.loading {
    opacity: 1.0;
  }
  #progress_bar .percent {
    background-color: #99ccff;
    height: auto;
    width: 0;
  }
</style>

<input type="file" id="files" name="file" />
<button onclick="abortRead();">Cancel read</button>
<div id="progress_bar"><div class="percent">0%</div></div>

<script>
  var reader;
  var progress = document.querySelector('.percent');

  function abortRead() {
    reader.abort();
  }

  function errorHandler(evt) {
    switch(evt.target.error.code) {
      case evt.target.error.NOT_FOUND_ERR:
        alert('File Not Found!');
        break;
      case evt.target.error.NOT_READABLE_ERR:
        alert('File is not readable');
        break;
      case evt.target.error.ABORT_ERR:
        break; // noop
      default:
        alert('An error occurred reading this file.');
    };
  }

  function updateProgress(evt) {
    // evt is an ProgressEvent.
    if (evt.lengthComputable) {
      var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
      // Increase the progress bar length.
      if (percentLoaded < 100) {
        progress.style.width = percentLoaded + '%';
        progress.textContent = percentLoaded + '%';
      }
    }
  }

  function handleFileSelect(evt) {
    // Reset progress indicator on new file selection.
    progress.style.width = '0%';
    progress.textContent = '0%';

    reader = new FileReader();
    reader.onerror = errorHandler;
    reader.onprogress = updateProgress;
    reader.onabort = function(e) {
      alert('File read cancelled');
    };
    reader.onloadstart = function(e) {
      document.getElementById('progress_bar').className = 'loading';
    };
    reader.onload = function(e) {
      // Ensure that the progress bar displays 100% at the end.
      progress.style.width = '100%';
      progress.textContent = '100%';
      setTimeout("document.getElementById('progress_bar').className='';", 2000);
    }

    // Read in the image file as a binary string.
    reader.readAsBinaryString(evt.target.files[0]);
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>

Beispiel: Überwachen des Lesefortschritts. Probieren Sie es aus!

0 %

Tipp: Verwenden Sie eine große Datei oder eine Ressource von einem Remotelaufwerk, um die Statusleiste in Aktion zu sehen.

Referenzen

Comments

0