Lee archivos en JavaScript

Seleccionar archivos e interactuar con ellos en el dispositivo local del usuario es una de las funciones más utilizadas de la Web. Permite a los usuarios seleccionar archivos y subirlos a un servidor, por ejemplo, cuando comparten fotos o envían documentos fiscales. También permite que los sitios los lean y manipulen sin tener que transferir los datos a través de la red. En esta página, se explica cómo usar JavaScript para interactuar con archivos.

La moderna API de File System Access

La API de File System Access proporciona una forma de leer y escribir archivos y directorios en el sistema local del usuario. Está disponible en la mayoría de los navegadores basados en Chromium, como Chrome y Edge. Para obtener más información, consulta The File System Access API.

Dado que la API de File System Access no es compatible con todos los navegadores, te recomendamos que uses browser-fs-access, una biblioteca auxiliar que usa la nueva API cuando está disponible y recurre a enfoques heredados cuando no lo está.

Trabaja con archivos de la forma clásica

En esta guía, se muestra cómo interactuar con archivos usando métodos heredados de JavaScript.

Seleccionar archivos

Existen dos formas principales de seleccionar archivos: con el elemento de entrada HTML y con una zona de arrastrar y soltar.

Elemento de entrada HTML

La forma más fácil para que los usuarios seleccionen archivos es con el elemento <input type="file">, que se admite en todos los navegadores principales. Cuando se hace clic en él, permite que el usuario seleccione un archivo o varios archivos si se incluye el atributo multiple, con la IU de selección de archivos integrada en el sistema operativo. Cuando el usuario termina de seleccionar uno o varios archivos, se activa el evento change del elemento. Puedes acceder a la lista de archivos desde event.target.files, que es un objeto FileList. Cada elemento de FileList es un objeto File.

<!-- The `multiple` attribute lets users select multiple files. -->
<input type="file" id="file-selector" multiple>
<script>
  const fileSelector = document.getElementById('file-selector');
  fileSelector.addEventListener('change', (event) => {
    const fileList = event.target.files;
    console.log(fileList);
  });
</script>

En el siguiente ejemplo, se permite que un usuario seleccione varios archivos con la IU de selección de archivos integrada en su sistema operativo y, luego, se registra cada archivo seleccionado en la consola.

Limita los tipos de archivos que pueden seleccionar los usuarios

En algunos casos, es posible que quieras limitar los tipos de archivos que los usuarios pueden seleccionar. Por ejemplo, una app de edición de imágenes solo debe aceptar imágenes, no archivos de texto. Para establecer restricciones de tipo de archivo, agrega un atributo accept al elemento de entrada para especificar qué tipos de archivo se aceptan:

<input type="file" id="file-selector" accept=".jpg, .jpeg, .png">

Arrastrar y soltar personalizado

En algunos navegadores, el elemento <input type="file"> también es un destino de soltar, lo que permite a los usuarios arrastrar y soltar archivos en tu app. Sin embargo, este destino de soltar es pequeño y puede ser difícil de usar. En cambio, después de proporcionar funciones principales con un elemento <input type="file">, puedes proporcionar una superficie de arrastrar y soltar personalizada grande.

Elige tu zona de entrega

La superficie de soltar depende del diseño de tu aplicación. Es posible que solo quieras que parte de la ventana sea una superficie de soltar, pero puedes usar toda la ventana.

Captura de pantalla de Squoosh, una app web de compresión de imágenes.
Squoosh convierte toda la ventana en una zona de colocación.

La app de compresión de imágenes Squoosh permite que el usuario arrastre una imagen a cualquier lugar de la ventana y haga clic en seleccionar una imagen para invocar el elemento <input type="file">. Cualquiera que sea la zona de destino que elijas, asegúrate de que el usuario sepa que puede arrastrar archivos a esa superficie.

Cómo definir la zona de soltar

Para habilitar un elemento como zona de arrastrar y soltar, crea objetos de escucha para dos eventos: dragover y drop. El evento dragover actualiza la IU del navegador para indicar visualmente que la acción de arrastrar y soltar está creando una copia del archivo. El evento drop se activa después de que el usuario suelta los archivos en la superficie. Al igual que con el elemento de entrada, puedes acceder a la lista de archivos desde event.dataTransfer.files, que es un objeto FileList. Cada elemento de FileList es un objeto File.

const dropArea = document.getElementById('drop-area');

dropArea.addEventListener('dragover', (event) => {
  event.stopPropagation();
  event.preventDefault();
  // Style the drag-and-drop as a "copy file" operation.
  event.dataTransfer.dropEffect = 'copy';
});

dropArea.addEventListener('drop', (event) => {
  event.stopPropagation();
  event.preventDefault();
  const fileList = event.dataTransfer.files;
  console.log(fileList);
});

event.stopPropagation() y event.preventDefault() detienen el comportamiento predeterminado del navegador y permiten que se ejecute tu código. Sin ellos, el navegador abandonaría tu página y abriría los archivos que el usuario soltó en la ventana del navegador.

Para obtener una demostración en vivo, consulta Arrastrar y soltar personalizado.

¿Qué sucede con los directorios?

Lamentablemente, no hay una buena manera de acceder a un directorio con JavaScript.

El atributo webkitdirectory del elemento <input type="file"> permite que el usuario elija un directorio o varios. Es compatible con la mayoría de los navegadores principales, excepto Firefox para Android y Safari en iOS.

Si la función de arrastrar y soltar está habilitada, es posible que un usuario intente arrastrar un directorio a la zona de destino. Cuando se activa el evento de soltar, incluye un objeto File para el directorio, pero no proporciona acceso a ninguno de los archivos del directorio.

Leer metadatos de archivos

El objeto File contiene metadatos sobre el archivo. La mayoría de los navegadores proporcionan el nombre del archivo, su tamaño y el tipo de MIME, aunque, según la plataforma, diferentes navegadores pueden proporcionar información diferente o adicional.

function getMetadataForFileList(fileList) {
  for (const file of fileList) {
    // Not supported in Safari for iOS.
    const name = file.name ? file.name : 'NOT SUPPORTED';
    // Not supported in Firefox for Android or Opera for Android.
    const type = file.type ? file.type : 'NOT SUPPORTED';
    // Unknown cross-browser support.
    const size = file.size ? file.size : 'NOT SUPPORTED';
    console.log({file, name, type, size});
  }
}

Puedes ver esto en acción en la demostración de input-type-file.

Cómo leer el contenido de un archivo

Usa FileReader para leer el contenido de un objeto File en la memoria. Puedes indicarle a FileReader que lea un archivo como un búfer de matriz, una URL de datos o texto:

function readImage(file) {
  // Check if the file is an image.
  if (file.type && !file.type.startsWith('image/')) {
    console.log('File is not an image.', file.type, file);
    return;
  }

  const reader = new FileReader();
  reader.addEventListener('load', (event) => {
    img.src = event.target.result;
  });
  reader.readAsDataURL(file);
}

En este ejemplo, se lee un File proporcionado por el usuario, luego se convierte en una URL de datos y se usa esa URL de datos para mostrar la imagen en un elemento img. Para aprender a verificar que el usuario haya seleccionado un archivo de imagen, consulta la demostración de read-image-file.

Supervisa el progreso de la lectura de un archivo

Cuando se leen archivos grandes, puede ser útil proporcionar cierta UX para indicarle al usuario qué tanto avanzó la lectura. Para ello, usa el evento progress que proporciona FileReader. El evento progress tiene dos propiedades: loaded (la cantidad leída) y total (la cantidad que se debe leer).

function readFile(file) {
  const reader = new FileReader();
  reader.addEventListener('load', (event) => {
    const result = event.target.result;
    // Do something with result
  });

  reader.addEventListener('progress', (event) => {
    if (event.loaded && event.total) {
      const percent = (event.loaded / event.total) * 100;
      console.log(`Progress: ${Math.round(percent)}`);
    }
  });
  reader.readAsDataURL(file);
}

Imagen hero de Vincent Botta de Unsplash