Impedisci al browser di caricare un file trascinato


194

Sto aggiungendo un uploader drag and drop html5 alla mia pagina.

Quando un file viene rilasciato nell'area di caricamento, tutto funziona alla grande.

Tuttavia, se accidentalmente trascino il file al di fuori dell'area di caricamento, il browser carica il file locale come se fosse una nuova pagina.

Come posso prevenire questo comportamento?

Grazie!


2
Sono solo curioso di sapere quale codice stai usando per gestire il caricamento drag / drop html5. Grazie.
robertwbradford,

Il problema che hai è causato dalla mancanza di e.dataTransfer () o dalla mancanza di preventDefault () su drop / dragenter / etc. eventi. Ma non posso dirlo senza un esempio di codice.
HoldOffHunger

Risposte:


314

È possibile aggiungere un listener di eventi alla finestra che chiama preventDefault()tutti gli eventi dragover e drop.
Esempio:

window.addEventListener("dragover",function(e){
  e = e || event;
  e.preventDefault();
},false);
window.addEventListener("drop",function(e){
  e = e || event;
  e.preventDefault();
},false);

45
dragover è il pezzo che mi mancava.
cgatian,

11
Confermo che sia dragovere dropgestori sono necessari per impedire al browser di caricare il file rilasciato. (Chrome ultimo 2015/08/03). La soluzione funziona anche su FF più recente.
Offirmo,

4
Funziona perfettamente e posso confermare che può essere utilizzato in combinazione con elementi di pagina che sono configurati per accettare eventi di rilascio, come quelli degli script di caricamento file drag-and-drop come resumable.js. È utile prevenire il comportamento predefinito del browser nei casi in cui un utente elimina accidentalmente un file che desidera caricare al di fuori della zona di rilascio effettiva del caricamento del file, quindi si chiede perché ora vede lo stesso file visualizzato direttamente nella finestra del browser ( presupponendo che sia stato eliminato un tipo di file compatibile come un'immagine o un video) anziché il comportamento previsto di vedere il caricamento del file.
bluebinary

15
Nota: questo disabilita anche il trascinamento dei file su a <input type="file" />. È necessario verificare se si e.targettratta di un input di file e lasciar passare tali eventi.
Sebastian Nowak,

6
che cosa ? perché windows dragover dovrebbe caricare il file? questo non ha senso ...
L.Trabacchin il

38

Dopo aver armeggiato molto, ho scoperto che questa era la soluzione più stabile:

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>

Impostando entrambi effectAllowe dropEffectincondizionatamente sulla finestra, la mia zona di rilascio non accetta più alcun dnd, indipendentemente dal fatto che le proprietà siano impostate nuove o meno.


e.dataTransfer () è il pezzo critico qui che rende questo lavoro, che la "risposta accettata" non ha menzionato.
HoldOffHunger

9

Per jQuery la risposta corretta sarà:

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

Qui return falsesi comporterà come event.preventDefault()e event.stopPropagation().


9

Per consentire il trascinamento della selezione solo su alcuni elementi, puoi fare qualcosa del tipo:

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);

Funziona perfettamente per me, ma aggiungerei anche il controllo per type = file, altrimenti puoi ancora trascinare gli input di testo
Andreas Zwerger

2

prova questo:

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

2

Prevenire tutte le operazioni di trascinamento della selezione per impostazione predefinita potrebbe non essere quello che desideri. È possibile verificare se l'origine di trascinamento è un file esterno, almeno in alcuni browser. Ho incluso una funzione per verificare se l'origine di trascinamento è un file esterno in questa risposta StackOverflow .

Modificando la risposta dell'aereo digitale, potresti fare qualcosa del genere:

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);

1
Che senso ha e || event;? Dove è eventdefinito? Non importa. Sembra che sia un oggetto globale in IE? Ho trovato questa citazione, "In Microsoft Visual Basic Scripting Edition (VBScript), you must access the event object through the window object." qui
1,21 gigawatt

2

Nota: sebbene l'OP non abbia richiesto una soluzione angolare, sono venuto qui a cercarlo. Quindi, questo è quello di condividere quella che ho trovato essere una soluzione praticabile, se usi Angular.

Nella mia esperienza, questo problema si presenta innanzitutto quando si aggiunge la funzionalità di rilascio dei file a una pagina. Pertanto, la mia opinione è che il componente che aggiunge questo, dovrebbe anche essere responsabile della prevenzione della caduta al di fuori della zona di caduta.

Nella mia soluzione, la zona di rilascio è un input con una classe, ma qualsiasi selettore non ambiguo funziona.

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';
    }
  }
}

I listener vengono aggiunti / rimossi automaticamente quando il componente viene creato / distrutto e altri componenti che utilizzano la stessa strategia nella stessa pagina non interferiscono tra loro a causa di stopPropagation ().


Funziona come un fascino !! Il browser cambia anche il cursore del mouse aggiungendo un'icona di ban che è così eccezionale !!
pti_jul

1

Per basarsi sul metodo "controlla la destinazione" delineato in poche altre risposte, ecco un metodo più generico / funzionale:

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

Chiamato come:

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])
);

Inoltre, potrebbe aggiungere un po 'ES6 qui: function preventDefaultExcept(...predicates){}. E poi preventDefaultExcept(isDropzone, isntParagraph)
usalo

0

Ho un HTML object( embed) che riempie la larghezza e l'altezza della pagina. La risposta di @ digital-plane funziona su normali pagine Web ma non se l'utente cade su un oggetto incorporato. Quindi avevo bisogno di una soluzione diversa.

Se passiamo all'utilizzo della fase di acquisizione degli eventi, possiamo ottenere gli eventi prima che l'oggetto incorporato li riceva (notate il truevalore alla fine della chiamata del listener di eventi):

// 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);

Utilizzando il seguente codice (basato sulla risposta di @ piano digitale) la pagina diventa un obiettivo di trascinamento, impedisce agli oggetti incorporati di acquisire gli eventi e quindi carica le nostre immagini:

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);

Testato su Firefox su Mac.


0

Sto usando un selettore di classe per più aree di upload, quindi la mia soluzione ha assunto questa forma meno pura

Basato sulla risposta di Axel Amthor, con dipendenza da jQuery (con 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);
        });
    },
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.