Lee archivos en JavaScript

Seleccionar archivos en el dispositivo local del usuario y, luego, interactuar con ellos 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 los datos a través de la red. En esta página, se explica cómo usar JavaScript para interactuar con archivos.

La API moderna 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. Si quieres 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, te recomendamos que uses browser-fs-access, una biblioteca auxiliar que usa la nueva API siempre que esté disponible y recurre a enfoques heredados cuando no lo está.

Cómo trabajar 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 usar el elemento <input type="file">, que es compatible con 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 su sistema operativo. Cuando el usuario termina de seleccionar uno o más 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 de su sistema operativo y, luego, registre cada archivo seleccionado en la consola.

Limita los tipos de archivos que los usuarios pueden seleccionar

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 objetivo de caída, lo que permite a los usuarios arrastrar y soltar archivos en tu app. Sin embargo, este objetivo de caída es pequeño y puede ser difícil de usar. En su lugar, después de proporcionar funciones principales con un elemento <input type="file">, puedes proporcionar una gran superficie de arrastrar y soltar personalizada.

Elige la zona de entrega

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 caída, pero puedes usar toda la ventana.

Captura de pantalla de Squoosh, una app web de compresión de imágenes.
Squioosh hace que toda la ventana sea una zona de caída.

La app de compresión de imágenes Squoosh permite que el usuario arrastre una imagen a cualquier parte de la ventana y haga clic en Seleccionar una imagen para invocar el elemento <input type="file">. Independientemente de lo que elijas como zona de caída, asegúrate de que el usuario tenga claro que puede arrastrar archivos a esa superficie.

Define la zona de caída

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 coloca 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 colocó en la ventana del navegador.

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

¿Qué ocurre 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 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 está habilitada la función de arrastrar y soltar, un usuario podría intentar arrastrar un directorio a la zona de soltar. 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, el tamaño y el tipo MIME, aunque, según la plataforma, los 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 demo de input-type-file.

Lee 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 buffer de array, 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 lo 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 seleccionó un archivo de imagen, consulta la demo de read-image-file.

Supervisa el progreso de la lectura de un archivo

Cuando leas archivos grandes, puede ser útil proporcionar cierta UX para indicarle al usuario el progreso de la lectura. Para ello, usa el evento progress que proporciona FileReader. El evento progress tiene dos propiedades: loaded (la cantidad que se lee) y total (la cantidad que se lee).

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