arrastre y suelte archivos en la entrada estándar de archivos html

163

En estos días podemos arrastrar y soltar archivos en un contenedor especial y subirlos con XHR 2. Muchos a la vez. Con barras de progreso en vivo, etc. Cosas muy interesantes. Ejemplo aquí.

Pero a veces no queremos tanta frescura. Lo que me gustaría es arrastrar y soltar archivos, muchos a la vez, en una entrada de archivo HTML estándar : <input type=file multiple>.

¿Es eso posible? ¿Hay alguna manera de 'llenar' la entrada del archivo con los nombres de archivo correctos (?) Desde la caída del archivo? (Las rutas de archivos completas no están disponibles por razones de seguridad del sistema de archivos).

¿Por qué? Porque me gustaría enviar un formulario normal. Para todos los navegadores y todos los dispositivos. Arrastrar y soltar es solo una mejora progresiva para mejorar y simplificar UX. El formulario estándar con entrada de archivo estándar (+ multipleatributo) estará allí. Me gustaría agregar la mejora HTML5.

editar
Sé que en algunos navegadores a veces (casi siempre) puede colocar archivos en la entrada del archivo. Sé que Chrome generalmente hace esto, pero a veces falla y luego carga el archivo en la página actual (un gran error si está completando un formulario). Quiero engañarlo y a prueba de navegador.

Rudie
fuente
1
Prepárese para un poco de dolor si desea incluir mac / safari en sus compatibilidades.
Shark8
1
@ Shark8 en realidad Safari / Mac es uno de los pocos navegadores que ya lo admiten.
Ricardo Tomasi
En realidad, ninguno de los navegadores lo admite. El campo de entrada del archivo es de solo lectura (por seguridad) y ese es el problema. Estúpida seguridad!
Rudie
2
Con esto quise decir "arrastrar y soltar archivos, muchos a la vez, en una entrada de archivo HTML estándar".
Ricardo Tomasi
3
arrastrar / soltar varios archivos para que input type="file" multiplefuncione bien en Safari
Lloyd

Respuestas:

71

Lo siguiente funciona en Chrome y FF, pero todavía no he encontrado una solución que cubra IE10 +:

// dragover and dragenter events need to have 'preventDefault' called
// in order for the 'drop' event to register. 
// See: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_operations#droptargets
dropContainer.ondragover = dropContainer.ondragenter = function(evt) {
  evt.preventDefault();
};

dropContainer.ondrop = function(evt) {
  // pretty simple -- but not for IE :(
  fileInput.files = evt.dataTransfer.files;

  // If you want to use some of the dropped files
  const dT = new DataTransfer();
  dT.items.add(evt.dataTransfer.files[0]);
  dT.items.add(evt.dataTransfer.files[3]);
  fileInput.files = dT.files;

  evt.preventDefault();
};
<!DOCTYPE html>
<html>
<body>
<div id="dropContainer" style="border:1px solid black;height:100px;">
   Drop Here
</div>
  Should update here:
  <input type="file" id="fileInput" />
</body>
</html>

Probablemente quiera usar addEventListenero jQuery (etc.) para registrar sus controladores evt, esto es solo por razones de brevedad.

jlb
fuente
3
Waaaaaaaat! ¿¡Eso funciona!? Eso es exactamente lo que estaba buscando. No funcionó hace 2 años. ¡Increíble! Por supuesto, no funciona en IE =) La pregunta importante: ¿hay una detección de funciones confiable ?, para que pueda ocultar la zona de caída en IE, porque no funcionará.
Rudie
Oh, un poco tarde entonces :) En este momento estoy usando simples controles de agente de usuario en JS. Por supuesto que tienes que probar MSIE , Trident/(IE11) y Edge/(IE12) ...
jlb
FF 48.0.2 (Mac) lanza "TypeError: estableciendo una propiedad que solo tiene un captador" en línea fileInput.files = evt.dataTransfer.files;. Safari y Chrome, sin embargo, ambos funcionan bien.
Risadinha
2
Este ejemplo no funciona en Firefox 45 en Linux, pero funciona para mí en Chrome. No recibo ningún error de consola, simplemente no muestra que se haya descartado ningún archivo.
Bryan Oakley
1
De hecho, hice una publicación para tratar de encontrar una solución, pero descubrí por mí mismo. Cambio bastante simple, solo fileInputs [index] = ... para pasar los datos del archivo a una entrada en particular y luego llamar a una función showNext para agregar una nueva entrada stackoverflow.com/a/43397640/6392779
nick
51

Hice una solución para esto.

La funcionalidad Arrastrar y soltar para este método solo funciona con Chrome, Firefox y Safari. (No sé si funciona con IE10), pero para otros navegadores, el botón "O haga clic aquí" funciona bien.

El campo de entrada simplemente sigue a su mouse cuando arrastra un archivo sobre un área, y también he agregado un botón.

Oportunidad de descomentar: 0; la entrada del archivo solo es visible para que pueda ver lo que está sucediendo.

BjarkeCK
fuente
Es por eso que también agregué un botón ^^ Pero sí, tienes razón. No lo usaría eather ... ¿O sí?
BjarkeCK
Desearía saber cómo se supone que funciona ... parece que todas las funciones de arrastrar / soltar tienen que lidiar con la adición del efecto de desplazamiento ... pero realmente no puedo decirlo. Se ve bien en el violín, pero no creo que pueda usarlo ya que necesito soportar Internet Explorer
nmz787
1
@PiotrKowalski Creo que eso podría desencadenar una llamada recursiva hasta que la pila de llamadas se desborde
John
2
Terminé usando solo el estilo. Hacer que la entrada sea 100% de ancho y alto funcionó mejor que moverlo.
Eddie
2
¿Hay alguna forma de deshacerse del "sin archivo elegido" que sigue flotando junto con nuestro puntero del mouse? @BjarkeCK
Abhishek Singh
27

Esta es la forma HTML5 "DTHML" de hacerlo. Entrada de formulario normal (que se lee solo como señaló Ricardo Tomasi). Luego, si se arrastra un archivo, se adjunta al formulario. Esto requerirá modificaciones en la página de acción para aceptar el archivo cargado de esta manera.

function readfiles(files) {
  for (var i = 0; i < files.length; i++) {
    document.getElementById('fileDragName').value = files[i].name
    document.getElementById('fileDragSize').value = files[i].size
    document.getElementById('fileDragType').value = files[i].type
    reader = new FileReader();
    reader.onload = function(event) {
      document.getElementById('fileDragData').value = event.target.result;}
    reader.readAsDataURL(files[i]);
  }
}
var holder = document.getElementById('holder');
holder.ondragover = function () { this.className = 'hover'; return false; };
holder.ondragend = function () { this.className = ''; return false; };
holder.ondrop = function (e) {
  this.className = '';
  e.preventDefault();
  readfiles(e.dataTransfer.files);
}
#holder.hover { border: 10px dashed #0c0 !important; }
<form method="post" action="http://example.com/">
  <input type="file"><input id="fileDragName"><input id="fileDragSize"><input id="fileDragType"><input id="fileDragData">
  <div id="holder" style="width:200px; height:200px; border: 10px dashed #ccc"></div>
</form>

Es aún más jefe si puede hacer que toda la ventana sea una zona de colocación, consulte ¿Cómo detecto un evento de arrastre HTML5 que entra y sale de la ventana, como lo hace Gmail?

William Entriken
fuente
1
Buena solución, pero no funciona en IE <10 porque IE 9 y menos no admite archivos HTML5 API :(
Desarrollador
1
Esta línea: document.getElementById ('fileDragData'). Value = files [i] .slice (); no es necesario, porque está reemplazado en la función
reader.onload
Aquí hay otra linda aplicación de arrastrar y soltar que NO involucra la carga de archivos. Vinculación por si acaso alguien quiere estudiar más. codepen.io/anon/pen/MOPvZK?editors=1010
William Entriken
1
La solución de IE 10 es degradar y solo mostrar elinput type=file
William Entriken
¿Me estoy perdiendo algo, o simplemente sobrescribe constantemente la .valuepropiedad con el archivo más reciente, cada vez que itera por el bucle anterior?
Kevin Burke
13

//----------App.js---------------------//
$(document).ready(function() {
    var holder = document.getElementById('holder');
    holder.ondragover = function () { this.className = 'hover'; return false; };
    holder.ondrop = function (e) {
      this.className = 'hidden';
      e.preventDefault();
      var file = e.dataTransfer.files[0];
      var reader = new FileReader();
      reader.onload = function (event) {
          document.getElementById('image_droped').className='visible'
          $('#image_droped').attr('src', event.target.result);
      }
      reader.readAsDataURL(file);
    };
});
.holder_default {
    width:500px; 
    height:150px; 
    border: 3px dashed #ccc;
}

#holder.hover { 
    width:400px; 
    height:150px; 
    border: 3px dashed #0c0 !important; 
}

.hidden {
    visibility: hidden;
}

.visible {
    visibility: visible;
}
<!DOCTYPE html>

<html>
    <head>
        <title> HTML 5 </title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.js"></script>
    </head>
    <body>
      <form method="post" action="http://example.com/">
        <div id="holder" style="" id="holder" class="holder_default">
          <img src="" id="image_droped" width="200" style="border: 3px dashed #7A97FC;" class=" hidden"/>
        </div>
      </form>
    </body>
</html>

Dipak
fuente
2
¿Qué le muestra al usuario? ¿Puedes hacer un violín o un ejemplo en línea?
Rudie
@Rudie, haga clic en ejecutar fragmento de código y arrastre y suelte una imagen para ver, se mostrará la vista previa de la imagen caída.
Dipak
6

En teoría, podría agregar un elemento superpuesto al <input/>, y luego usar su dropevento para capturar los archivos (usando la API de archivos) y pasarlos a la filesmatriz de entrada .

Excepto que la entrada de un archivo es de solo lectura . Este es un viejo problema.

Sin embargo, puede omitir el control del formulario por completo y cargarlo a través de XHR (no estoy seguro sobre el soporte para eso):

También puede usar un elemento en el área circundante para cancelar el evento de caída en Chrome y evitar el comportamiento predeterminado de cargar el archivo.

Dejar caer varios archivos sobre la entrada ya funciona en Safari y Firefox.

Ricardo Tomasi
fuente
66
Como dije en la pregunta: conozco XHR2 y no quiero usarlo. Supongo que la parte importante: "la entrada del archivo es de solo lectura". Eso apesta ... ¡Cancelar el evento de caída no es una mala idea! No tan bueno como esperaba, pero probablemente el mejor. Dejar caer varios archivos también funciona en Chrome. Chrome ahora también permite cargar directorios. Todo muy confuso y sin ayudar a mi caso = (
Rudie
5

Esto es con lo que salí.

Usando Jquery y Html. Esto lo agregará a los archivos de inserción.

var dropzone = $('#dropzone')


dropzone.on('drag dragstart dragend dragover dragenter dragleave drop', function(e) {
    e.preventDefault();
    e.stopPropagation();
  })

dropzone.on('dragover dragenter', function() {
    $(this).addClass('is-dragover');
  })
dropzone.on('dragleave dragend drop', function() {
    $(this).removeClass('is-dragover');
  })  
  
dropzone.on('drop',function(e) {
	var files = e.originalEvent.dataTransfer.files;
	// Now select your file upload field 
	// $('input_field_file').prop('files',files)
  });
input {	margin: 15px 10px !important;}

.dropzone {
	padding: 50px;
	border: 2px dashed #060;
}

.dropzone.is-dragover {
  background-color: #e6ecef;
}

.dragover {
	bg-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div class="" draggable='true' style='padding: 20px'>
	<div id='dropzone' class='dropzone'>
		Drop Your File Here
	</div>
	</div>

Lionel Yeo
fuente
4

Para una solución solo CSS:

<div class="file-area">
    <input type="file">
    <div class="file-dummy">
        <span class="default">Click to select a file, or drag it here</span>
        <span class="success">Great, your file is selected</span>
    </div>
</div>

.file-area {
    width: 100%;
    position: relative;
    font-size: 18px;
}
.file-area input[type=file] {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: 0;
    cursor: pointer;
}
.file-area .file-dummy {
    width: 100%;
    padding: 50px 30px;
    border: 2px dashed #ccc;
    background-color: #fff;
    text-align: center;
    transition: background 0.3s ease-in-out;
}
.file-area .file-dummy .success {
    display: none;
}
.file-area:hover .file-dummy {
    border: 2px dashed #1abc9c;
}
.file-area input[type=file]:valid + .file-dummy {
    border-color: #1abc9c;
}
.file-area input[type=file]:valid + .file-dummy .success {
    display: inline-block;
}
.file-area input[type=file]:valid + .file-dummy .default {
    display: none;
}

Modificado de https://codepen.io/Scribblerockerz/pen/qdWzJw

Jonathan
fuente
4

Sé que algunos trucos funcionan en Chrome:

Al soltar archivos en la zona de colocación, obtiene un dataTransfer.filesobjeto, que es un FileListtipo de objeto, que contiene todos los archivos que arrastró. Mientras tanto, el <input type="file" />elemento tiene la propiedad files, que es el mismo FileListtipo de objeto.

Entonces, simplemente puede asignar el dataTransfer.filesobjeto a la input.filespropiedad.

Timur Gilauri
fuente
3
Sí, lo hace en estos días. No es un truco Muy intencional También muy intencionalmente muy restringido. No puede agregar archivos a la lista ni mutar la lista. Arrastrar y soltar puede recordar archivos y agregarlos, pero input.filesno puede = (
Rudie
3

Para cualquiera que esté buscando hacer esto en 2018, tengo una solución mucho mejor y más simple que todas las cosas viejas publicadas aquí. Puede crear un cuadro atractivo de arrastrar y soltar con solo HTML, JavaScript y CSS.

(Solo funciona en Chrome hasta ahora)

Comencemos con el HTML.

<div>
<input type="file" name="file" id="file" class="file">
<span id="value"></span>
</div>

Luego llegaremos al estilo.

    .file {
        width: 400px;
        height: 50px;
        background: #171717;
        padding: 4px;
        border: 1px dashed #333;
        position: relative;
        cursor: pointer;
    }

    .file::before {
        content: '';
        position: absolute;
        background: #171717;
        font-size: 20px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100%;
        height: 100%;
    }

    .file::after {
        content: 'Drag & Drop';
        position: absolute;
        color: #808080;
        font-size: 20px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }

Después de hacer esto, ya se ve bien. Pero me imagino que te gustaría ver qué archivo subiste en realidad, así que haremos algo de JavaScript. ¿Recuerdas ese lapso de valor pfp? Ahí es donde imprimiremos el nombre del archivo.

let file = document.getElementById('file');
file.addEventListener('change', function() {
    if(file && file.value) {
        let val = file.files[0].name;
        document.getElementById('value').innerHTML = "Selected" + val;
    }
});

Y eso es.

Miguel
fuente
Me sale un TypeError no capturado: no se puede leer la propiedad 'addEventListener' de nulo cuando uso este código, en Chrome, ¿no funciona en las últimas versiones de Chrome?
Fight Fire With Fire
Funciona bien para mí en la última versión de Chrome. Asegúrate de usar las ID correctas
Michael
1

Impresionante trabajo de @BjarkeCK. Hice algunas modificaciones a su trabajo, para usarlo como método en jquery:

$.fn.dropZone = function() {
  var buttonId = "clickHere";
  var mouseOverClass = "mouse-over";

  var dropZone = this[0];
  var $dropZone = $(dropZone);
  var ooleft = $dropZone.offset().left;
  var ooright = $dropZone.outerWidth() + ooleft;
  var ootop = $dropZone.offset().top;
  var oobottom = $dropZone.outerHeight() + ootop;
  var inputFile = $dropZone.find("input[type='file']");
  dropZone.addEventListener("dragleave", function() {
    this.classList.remove(mouseOverClass);
  });
  dropZone.addEventListener("dragover", function(e) {
    console.dir(e);
    e.preventDefault();
    e.stopPropagation();
    this.classList.add(mouseOverClass);
    var x = e.pageX;
    var y = e.pageY;

    if (!(x < ooleft || x > ooright || y < ootop || y > oobottom)) {
      inputFile.offset({
        top: y - 15,
        left: x - 100
      });
    } else {
      inputFile.offset({
        top: -400,
        left: -400
      });
    }

  }, true);
  dropZone.addEventListener("drop", function(e) {
    this.classList.remove(mouseOverClass);
  }, true);
}

$('#drop-zone').dropZone();

Violín de trabajo

SR verde
fuente
1
FYI: el enlace Fiddle está roto.
jimiayler
1

Pocos años después, he creado esta biblioteca para colocar archivos en cualquier elemento HTML.

Puedes usarlo como

const Droppable = require('droppable');

const droppable = new Droppable({
    element: document.querySelector('#my-droppable-element')
})

droppable.onFilesDropped((files) => {
    console.log('Files were dropped:', files);
});

// Clean up when you're done!
droppable.destroy();
Joel Hernández
fuente
¿Cómo se obtiene el archivo seleccionado más adelante al enviar el formulario?
Nikhil VJ
-1

Lo que podría hacer es mostrar una entrada de archivo y superponerla con su área de colocación transparente, teniendo cuidado de usar un nombre como file[1]. {Asegúrese de tener el enctype="multipart/form-data"interior de su etiqueta FORM.}

Luego, haga que el área de colocación maneje los archivos adicionales creando dinámicamente más entradas de archivo para los archivos 2..number_of_files, asegúrese de usar el mismo nombre base, rellenando el atributo value de manera apropiada.

Por último (front-end) envíe el formulario.


Todo lo que se requiere para manejar este método es alterar su procedimiento para manejar una matriz de archivos.

Tiburón8
fuente
1
La entrada del archivo tiene un multipleatributo en estos días. No necesita más de 1 entrada de archivo. Sin embargo, ese no es el problema. ¿Cómo consigo los Fileobjetos en la entrada del archivo? Estoy pensando que esto requiere algún ejemplo de código ...
Rudie
1
@Rudie no puedes, ese es el problema.
Ricardo Tomasi
1
¿No puedes qué? ¿Múltiple? Sí tu puedes. Acabo de decir que. El múltiple no es el problema. Obtener los archivos de un objeto File (arrastrado) en una entrada de archivo, ese es el problema.
Rudie
@Rudie puede arrastrar archivos a una entrada de archivo con Chrome / FF (usando la filespropiedad), pero no me las he arreglado en IE, ¿ha tenido suerte?
jlb
1
@jlb ¿Qué quiere decir "usar la propiedad de archivos"? ¿Podrías responder con un código relevante? Lo que estaba buscando no funciona / existe en ningún navegador.
Rudie