Lee archivos en JavaScript

La selección de archivos y la interacción con ellos en el dispositivo local del usuario es una de las funciones más usadas de la Web. Permite que los usuarios seleccionen archivos y los suban 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 datos a través de la red. En esta página, se explica cómo usar JavaScript para interactuar con los archivos.

La moderna API de File System Access

La API de File System Access proporciona una forma de leer y escribir en archivos y directorios del 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 la API de File System Access.

Debido a que la API de File System Access no es compatible con todos los navegadores, recomendamos utilizar browser-fs-access, una biblioteca de ayuda que utiliza la nueva API siempre que esté disponible y recurre a los 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 mediante los métodos heredados de JavaScript.

Selecciona archivos

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

Elemento de entrada HTML

La forma más fácil de seleccionar archivos para los usuarios es usar el elemento <input type="file">, que es compatible con todos los navegadores principales. Cuando se hace clic en él, permite al usuario seleccionar uno o varios archivos si se incluye el atributo multiple, mediante la IU de selección de archivos integrada del 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 a un usuario seleccionar varios archivos mediante la IU de selección de archivos integrada de su sistema operativo y, luego, registrar cada archivo seleccionado en la consola.

Limita los tipos de archivos que los usuarios pueden seleccionar

En algunos casos, se recomienda 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. Si deseas establecer restricciones de tipo de archivo, agrega un atributo accept al elemento de entrada para especificar qué tipos de archivos se aceptan:

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

Función de arrastrar y soltar personalizada

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

Elige tu zona de drop

La superficie de caída depende del diseño de tu aplicación. Es posible que solo quieras que parte de la ventana sea una superficie de drop, 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 drop.

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

Define la zona de la función de soltar

Si deseas habilitar un elemento como una 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 saldría de tu página y abriría los archivos que el usuario soltó en la ventana del navegador.

Para ver una demostración en vivo, consulta Cómo arrastrar y soltar de forma personalizada.

¿Qué ocurre con los directorios?

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

El atributo webkitdirectory del elemento <input type="file"> le permite al usuario elegir uno o varios directorios. 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, un usuario podría intentar arrastrar un directorio a la zona para soltar. Cuando se activa el evento de soltar, se incluye un objeto File para el directorio, pero no proporciona acceso a ninguno de los archivos del directorio.

Lee los metadatos de un archivo

El objeto File contiene metadatos sobre el archivo. La mayoría de los navegadores proporcionan el nombre de archivo, el tamaño del archivo y el tipo de MIME, aunque, según la plataforma, los distintos 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 array, una URL de datos o un 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 una File proporcionada 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.

Cómo supervisar el progreso de la lectura de un archivo

Cuando se leen archivos grandes, puede ser útil proporcionar UX para indicarle al usuario qué tan lejos ha progresado la lectura. Para eso, usa el evento progress que proporciona FileReader. El evento progress tiene dos propiedades: loaded (la cantidad leída) y total (la cantidad que se 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);
}

Hero image de Vincent Botta de Unsplash.