Evitar que el navegador cargue un archivo de arrastrar y soltar

194

Estoy agregando un cargador html5 de arrastrar y soltar a mi página.

Cuando un archivo se deja caer en el área de carga, todo funciona muy bien.

Sin embargo, si accidentalmente dejo caer el archivo fuera del área de carga, el navegador carga el archivo local como si fuera una página nueva.

¿Cómo puedo prevenir este comportamiento?

¡Gracias!

Travis
fuente
2
Solo tengo curiosidad por saber qué código está utilizando para manejar la carga de arrastrar / soltar html5. Gracias.
robertwbradford
El problema que tiene se debe a que falta e.dataTransfer () o falta un preventDefault () en drop / dragenter / etc. eventos. Pero no puedo decir sin una muestra de código.
HoldOffHunger

Respuestas:

312

Puede agregar un detector de eventos a la ventana que llama preventDefault()a todos los eventos de dragover y drop.
Ejemplo:

window.addEventListener("dragover",function(e){
  e = e || event;
  e.preventDefault();
},false);
window.addEventListener("drop",function(e){
  e = e || event;
  e.preventDefault();
},false);
Plano digital
fuente
44
Dragover es la pieza que me faltaba.
cgatian
11
Confirmo que se necesitan ambos dragovery dropcontroladores para evitar que el navegador cargue el archivo descartado. (Chrome, más reciente 2015/08/03). La solución también funciona en FF más reciente.
Offirmo
44
Esto funciona perfectamente, y puedo confirmar que se puede usar en combinación con elementos de página que están configurados para aceptar eventos de caída, como los de scripts de carga de archivos de arrastrar y soltar como resumable.js. Es útil para evitar el comportamiento predeterminado del navegador en los casos en que un usuario suelta accidentalmente un archivo que desea cargar fuera de la zona de descarga real de carga de archivos, y luego se pregunta por qué ahora ven ese mismo archivo directamente en la ventana del navegador ( suponiendo que se eliminó un tipo de archivo compatible, como una imagen o un video), en lugar del comportamiento esperado de ver la carga de su archivo.
bluebinary
15
Nota: esto también deshabilita arrastrar archivos a un <input type="file" />. Es necesario verificar si e.targetes una entrada de archivo y dejar pasar tales eventos.
Sebastian Nowak
66
Qué ? ¿Por qué debería cargar el archivo dragover de Windows? esto no tiene sentido ...
L.Trabacchin
37

Después de mucho jugar, descubrí que esta es la solución más estable:

var dropzoneId = "dropzone";

window.addEventListener("dragenter", function(e) {
  if (e.target.id != dropzoneId) {
    e.preventDefault();
    e.dataTransfer.effectAllowed = "none";
    e.dataTransfer.dropEffect = "none";
  }
}, false);

window.addEventListener("dragover", function(e) {
  if (e.target.id != dropzoneId) {
    e.preventDefault();
    e.dataTransfer.effectAllowed = "none";
    e.dataTransfer.dropEffect = "none";
  }
});

window.addEventListener("drop", function(e) {
  if (e.target.id != dropzoneId) {
    e.preventDefault();
    e.dataTransfer.effectAllowed = "none";
    e.dataTransfer.dropEffect = "none";
  }
});
<div id="dropzone">...</div>

Establecer ambos effectAllowe dropEffectincondicionalmente en la ventana hace que mi zona de colocación ya no acepte ningún dnd, independientemente de si las propiedades se configuran como nuevas o no.

Axel Amthor
fuente
e.dataTransfer () es la pieza crítica aquí que hace que esto funcione, que la "respuesta aceptada" no mencionó.
HoldOffHunger
9

Para jQuery la respuesta correcta será:

$(document).on({
    dragover: function() {
        return false;
    },
    drop: function() {
        return false;
    }
});

Aquí return falsese comportará como event.preventDefault()y event.stopPropagation().

Visión
fuente
9

Para permitir arrastrar y soltar solo en algunos elementos, puede hacer algo como:

window.addEventListener("dragover",function(e){
  e = e || event;
  console.log(e);
  if (e.target.tagName != "INPUT") { // check which element is our target
    e.preventDefault();
  }
},false);
window.addEventListener("drop",function(e){
  e = e || event;
  console.log(e);
  if (e.target.tagName != "INPUT") {  // check which element is our target
    e.preventDefault();
  }  
},false);
entusiasta
fuente
Funciona perfecto para mí, pero también agregaría check para type = file, de lo contrario aún puede arrastrar a las entradas de texto
Andreas Zwerger
2

prueba esto:

document.body.addEventListener('drop', function(e) {
    e.preventDefault();
}, false);
moe
fuente
2

La prevención de todas las operaciones de arrastrar y soltar de forma predeterminada puede no ser lo que desea. Es posible verificar si la fuente de arrastre es un archivo externo, al menos en algunos navegadores. He incluido una función para verificar si la fuente de arrastre es un archivo externo en esta respuesta de StackOverflow .

Modificando la respuesta de Digital Plane, podría hacer algo como esto:

function isDragSourceExternalFile() {
     // Defined here: 
     // https://stackoverflow.com/a/32044172/395461
}

window.addEventListener("dragover",function(e){
    e = e || event;
    var IsFile = isDragSourceExternalFile(e.originalEvent.dataTransfer);
    if (IsFile) e.preventDefault();
},false);
window.addEventListener("drop",function(e){
    e = e || event;
    var IsFile = isDragSourceExternalFile(e.originalEvent.dataTransfer);
    if (IsFile) e.preventDefault();
},false);
Shannon Matthews
fuente
1
¿De qué sirve e || event;? ¿Dónde se eventdefine? No importa. Parece que es un objeto global en IE? Encontré esta cita, "In Microsoft Visual Basic Scripting Edition (VBScript), you must access the event object through the window object." aquí
1.21 gigavatios
2

Nota: Aunque el OP no solicitó una solución angular, vine aquí buscando eso. Entonces, esto es para compartir lo que encontré como una solución viable, si usa Angular.

En mi experiencia, este problema surge primero cuando agrega la funcionalidad de caída de archivos a una página. Por lo tanto, mi opinión es que el componente que agrega esto, también debería ser responsable de prevenir la caída fuera de la zona de caída.

En mi solución, la zona de colocación es una entrada con una clase, pero cualquier selector inequívoco funciona.

import { Component, HostListener } from '@angular/core';
//...

@Component({
  template: `
    <form>
      <!-- ... -->
      <input type="file" class="dropzone" />
    </form>
  `
})
export class MyComponentWithDropTarget {

  //...

  @HostListener('document:dragover', ['$event'])
  @HostListener('drop', ['$event'])
  onDragDropFileVerifyZone(event) {
    if (event.target.matches('input.dropzone')) {
      // In drop zone. I don't want listeners later in event-chain to meddle in here
      event.stopPropagation();
    } else {
      // Outside of drop zone! Prevent default action, and do not show copy/move icon
      event.preventDefault();
      event.dataTransfer.effectAllowed = 'none';
      event.dataTransfer.dropEffect = 'none';
    }
  }
}

Los oyentes se agregan / eliminan automáticamente cuando se crea / destruye el componente, y otros componentes que usan la misma estrategia en la misma página no interfieren entre sí debido a stopPropagation ().

Superole
fuente
¡Esto funciona de maravilla! ¡El navegador incluso cambia el cursor del mouse agregando un ícono de prohibición que es genial!
pti_jul
1

Para construir sobre el método "verificar el objetivo" descrito en algunas otras respuestas, aquí hay un método más genérico / funcional:

function preventDefaultExcept(predicates) {
  return function (e) {
    var passEvery = predicates.every(function (predicate) { return predicate(e); })
    if (!passEvery) {
      e.preventDefault();
    }
  };
}

Llamado como:

function isDropzone(e) { return e.target.id === 'dropzone'; }
function isntParagraph(e) { return e.target.tagName !== 'p'; }

window.addEventListener(
  'dragover',
  preventDefaultExcept([isDropzone, isntParagraph])
);
window.addEventListener(
  'drop',
  preventDefaultExcept([isDropzone])
);
scott_trinh
fuente
Además, podría añadir un poco de ES6 aquí: function preventDefaultExcept(...predicates){}. Y luego preventDefaultExcept(isDropzone, isntParagraph)
úsalo
0

Tengo un HTML object( embed) que llena el ancho y el alto de la página. La respuesta de @ digital-plane funciona en páginas web normales, pero no si el usuario cae sobre un objeto incrustado. Entonces necesitaba una solución diferente.

Si cambiamos al uso de la fase de captura de eventos , podemos obtener los eventos antes de que el objeto incrustado los reciba (observe el truevalor al final de la llamada de escucha de eventos):

// document.body or window
document.body.addEventListener("dragover", function(e){
  e = e || event;
  e.preventDefault();
  console.log("over true");
}, true);

document.body.addEventListener("drop", function(e){
  e = e || event;
  e.preventDefault();
  console.log("drop true");
}, true);

Usando el siguiente código (basado en la respuesta de @ digital-plane) la página se convierte en un objetivo de arrastre, evita que las incrustaciones de objetos capturen los eventos y luego carga nuestras imágenes:

document.body.addEventListener("dragover", function(e){
  e = e || event;
  e.preventDefault();
  console.log("over true");
}, true);

document.body.addEventListener("drop",function(e){
  e = e || event;
  e.preventDefault();
  console.log("Drop true");

  // begin loading image data to pass to our embed
  var droppedFiles = e.dataTransfer.files;
  var fileReaders = {};
  var files = {};
  var reader;

  for (var i = 0; i < droppedFiles.length; i++) {
    files[i] = droppedFiles[i]; // bc file is ref is overwritten
    console.log("File: " + files[i].name + " " + files[i].size);
    reader = new FileReader();
    reader.file = files[i]; // bc loadend event has no file ref

    reader.addEventListener("loadend", function (ev, loadedFile) {
      var fileObject = {};
      var currentReader = ev.target;

      loadedFile = currentReader.file;
      console.log("File loaded:" + loadedFile.name);
      fileObject.dataURI = currentReader.result;
      fileObject.name = loadedFile.name;
      fileObject.type = loadedFile.type;
      // call function on embed and pass file object
    });

    reader.readAsDataURL(files[i]);
  }

}, true);

Probado en Firefox en Mac.

1,21 gigavatios
fuente
0

Estoy usando un selector de clase para múltiples áreas de carga, por lo que mi solución tomó esta forma menos pura

Basado en la respuesta de Axel Amthor, con dependencia de jQuery (alias a $)

_stopBrowserFromOpeningDragAndDropPDFFiles = function () {

        _preventDND = function(e) {
            if (!$(e.target).is($(_uploadBoxSelector))) {
                e.preventDefault();
                e.dataTransfer.effectAllowed = 'none';
                e.dataTransfer.dropEffect = 'none';
            }
        };

        window.addEventListener('dragenter', function (e) {
            _preventDND(e);
        }, false);

        window.addEventListener('dragover', function (e) {
            _preventDND(e);
        });

        window.addEventListener('drop', function (e) {
            _preventDND(e);
        });
    },
hngr18
fuente