Cómo arrastrar y soltar archivos

Las interfaces de arrastrar y soltar HTML permiten que las aplicaciones web acepten archivos arrastrados y soltados en una página web. Durante una operación de arrastrar y soltar, los elementos del directorio y del archivo arrastrados se asocian con entradas de archivos y de directorios, respectivamente. Cuando se trata de arrastrar y soltar archivos en el navegador, hay dos formas de hacerlo: la moderna y la clásica.

El método DataTransferItem.getAsFileSystemHandle() muestra una promesa con un objeto FileSystemFileHandle si el elemento arrastrado es un archivo y una promesa con un objeto FileSystemDirectoryHandle si el elemento arrastrado es un directorio. Estos controladores te permiten leer el archivo o directorio y, si lo deseas, volver a escribirlo. Ten en cuenta que el elemento DataTransferItem.kind de la interfaz de arrastrar y soltar será "file" para los archivos y directorios, mientras que el FileSystemHandle.kind de la API de File System Access será "file" para los archivos y "directory" para los directorios.

Navegadores compatibles

  • 86
  • 86
  • x
  • x

Origen

La forma clásica

Usa el método DataTransferItem.getAsFile() clásico

El método DataTransferItem.getAsFile() muestra el objeto File del elemento de datos de arrastre. Si el elemento no es un archivo, este método muestra null. Si bien puedes leer el archivo, no hay forma de volver a escribirlo. Este método tiene la desventaja de que no admite directorios.

Navegadores compatibles

  • 11
  • 12
  • 50
  • 5.1

Origen

Mejora progresiva

En el siguiente fragmento, se usa el método DataTransferItem.getAsFileSystemHandle() de la API de File System Access moderna cuando es compatible, luego recurre al método DataTransferItem.webkitGetAsEntry() no estándar y, por último, recurre al método clásico DataTransferItem.getAsFile(). Asegúrate de verificar el tipo de cada handle, ya que puede ser cualquiera de las siguientes opciones:

  • FileSystemFileHandle cuando se elige la ruta de código moderna.
  • File cuando se elige la ruta de acceso del código clásico.

Todos los tipos tienen una propiedad name, por lo que registrarla está bien y siempre funcionará.

// Run feature detection.
const supportsFileSystemAccessAPI =
 
'getAsFileSystemHandle' in DataTransferItem.prototype;

// This is the drag and drop zone.
const elem = document.querySelector('main');

 
// Prevent navigation.
elem
.addEventListener('dragover', (e) => {
  e
.preventDefault();
});

// Visually highlight the drop zone.
elem
.addEventListener('dragenter', (e) => {
  elem
.style.outline = 'solid red 1px';
});

// Visually unhighlight the drop zone.
elem
.addEventListener('dragleave', (e) => {
  elem
.style.outline = '';
});

// This is where the drop is handled.
elem
.addEventListener('drop', async (e) => {
 
// Prevent navigation.
  e
.preventDefault();
 
// Unhighlight the drop zone.
  elem
.style.outline = '';
 
// Prepare an array of promises…
 
const fileHandlesPromises = [...e.dataTransfer.items]
   
// …by including only files (where file misleadingly means actual file _or_
   
// directory)…
   
.filter((item) => item.kind === 'file')
   
// …and, depending on previous feature detection…
   
.map((item) =>
      supportsFileSystemAccessAPI
       
// …either get a modern `FileSystemHandle`…
       
? item.getAsFileSystemHandle()
       
// …or a classic `File`.
       
: item.getAsFile(),
   
);
 
// Loop over the array of promises.
 
for await (const handle of fileHandlesPromises) {
   
// This is where we can actually exclusively act on the files.
   
if (handle.kind === 'file' || handle.isFile) {
      console
.log(`File: ${handle.name}`);
   
}
 
}
});

Lecturas adicionales

Demostración

<!DOCTYPE html>
<html lang="en">
 
<head>
   
<meta charset="utf-8" />
   
<meta name="viewport" content="width=device-width, initial-scale=1" />
   
<title>How to drag and drop files</title>
 
</head>
 
<body>
   
<main>
     
<h1>How to drag and drop files</h1>
     
<p>Drag and drop one or multiple files onto the page.</p>
     
<pre></pre>
   
</main>
 
</body>
</html>

       
:root {
 
color-scheme: dark light;
 
box-sizing: border-box;
}

*,
*:before,
*:after {
 
box-sizing: inherit;
}

body
{
 
margin: 0;
 
padding: 1rem;
 
font-family: system-ui, sans-serif;
 
line-height: 1.5;
 
min-height: 100vh;
 
display: flex;
 
flex-direction: column;
}

img
,
video
{
 
height: auto;
 
max-width: 100%;
}

main
{
 
flex-grow: 1;
}

footer
{
 
margin-top: 1rem;
 
border-top: solid CanvasText 1px;
 
font-size: 0.8rem;
}
       

       
const supportsFileSystemAccessAPI =
 
"getAsFileSystemHandle" in DataTransferItem.prototype;
const supportsWebkitGetAsEntry =
 
"webkitGetAsEntry" in DataTransferItem.prototype;

const elem = document.querySelector("main");
const debug = document.querySelector("pre");

elem
.addEventListener("dragover", (e) => {
 
// Prevent navigation.
  e
.preventDefault();
});

elem
.addEventListener("dragenter", (e) => {
  elem
.style.outline = "solid red 1px";
});

elem
.addEventListener("dragleave", (e) => {
  elem
.style.outline = "";
});

elem
.addEventListener("drop", async (e) => {
  e
.preventDefault();
  elem
.style.outline = "";
 
const fileHandlesPromises = [...e.dataTransfer.items]
   
.filter((item) => item.kind === "file")
   
.map((item) =>
      supportsFileSystemAccessAPI
       
? item.getAsFileSystemHandle()
       
: supportsWebkitGetAsEntry
       
? item.webkitGetAsEntry()
       
: item.getAsFile()
   
);

 
for await (const handle of fileHandlesPromises) {
   
if (handle.kind === "directory" || handle.isDirectory) {
      console
.log(`Directory: ${handle.name}`);
      debug
.textContent += `Directory: ${handle.name}\n`;
   
} else {
      console
.log(`File: ${handle.name}`);
      debug
.textContent += `File: ${handle.name}\n`;
   
}
 
}
});