Impedir que o navegador carregue um arquivo arrastar e soltar

194

Estou adicionando um recurso de arrastar e soltar html5 à minha página.

Quando um arquivo é solto na área de upload, tudo funciona muito bem.

No entanto, se eu soltar acidentalmente o arquivo fora da área de upload, o navegador carregará o arquivo local como se fosse uma nova página.

Como posso evitar esse comportamento?

Obrigado!

Travis
fonte
2
Apenas curioso que código você está usando para lidar com o upload de arrastar / soltar html5. Obrigado.
31811 Robertwbradford
O problema que você tem é causado pela falta de e.dataTransfer () ou por um preventDefault () no drop / dragenter / etc. eventos. Mas não posso dizer sem um exemplo de código.
HoldOffHunger

Respostas:

312

Você pode adicionar um ouvinte de eventos à janela que chama preventDefault()todos os eventos de arrastar e soltar.
Exemplo:

window.addEventListener("dragover",function(e){
  e = e || event;
  e.preventDefault();
},false);
window.addEventListener("drop",function(e){
  e = e || event;
  e.preventDefault();
},false);
Digital Plane
fonte
44
dragover é a peça que estava faltando.
cgatian
11
Confirmo que ambos dragovere dropmanipuladores são necessários para impedir que o navegador carregue o arquivo descartado. (Chrome mais recente em 2015/08/03). A solução também funciona no FF mais recente.
Offirmo
4
Isso funciona perfeitamente e posso confirmar que ele pode ser usado em combinação com elementos de página configurados para aceitar eventos de descarte, como os de scripts de upload de arquivos de arrastar e soltar, como resumable.js. É útil evitar o comportamento padrão do navegador nos casos em que um usuário descarta acidentalmente um arquivo que deseja enviar para fora da zona de recebimento real de upload de arquivos e depois se pergunta por que agora vê o mesmo arquivo renderizado diretamente na janela do navegador ( supondo que um tipo de arquivo compatível, como uma imagem ou um vídeo, foi eliminado), em vez do comportamento esperado ao ver o upload do arquivo.
bluebinary
15
Nota: isso também desativa arrastar arquivos para a <input type="file" />. É necessário verificar se e.targeté uma entrada de arquivo e deixar esses eventos passarem.
Sebastian Nowak
6
o que ? por que a janela arrastar a janela deve carregar o arquivo? isso não faz sentido ...
L.Trabacchin
37

Depois de muita discussão, achei a solução mais estável:

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>

Definir ambos effectAllowe dropEffectincondicionalmente na janela faz com que minha zona de recebimento não aceite mais nenhum dnd, independentemente de as propriedades serem definidas como novas ou não.

Axel Amthor
fonte
e.dataTransfer () é a parte crítica aqui que faz esse trabalho, que a "resposta aceita" não mencionou.
HoldOffHunger
9

Para jQuery, a resposta correta será:

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

Aqui return falsese comportará como event.preventDefault()e event.stopPropagation().

Visão
fonte
9

Para permitir arrastar e soltar apenas em alguns elementos, você pode fazer 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);
entusiasmo1
fonte
Funciona perfeito para mim, mas eu também adicionaria a verificação do tipo = arquivo, caso contrário você ainda pode arrastar para entradas de texto
Andreas Zwerger 24/04
2

tente isto:

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

Impedir todas as operações de arrastar e soltar por padrão pode não ser o que você deseja. É possível verificar se a fonte de arrastar é um arquivo externo, pelo menos em alguns navegadores. Incluí uma função para verificar se a fonte de arrasto é um arquivo externo nesta resposta do StackOverflow .

Modificando a resposta do Digital Plane, você pode fazer algo assim:

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
fonte
1
Qual o sentido disso e || event;? Onde é eventdefinido? Deixa pra lá. Parece que é um objeto global no IE? Encontrei esta citação, "In Microsoft Visual Basic Scripting Edition (VBScript), you must access the event object through the window object." aqui
1,21 gigawatts
2

Nota: Embora o OP não tenha solicitado uma solução Angular, eu vim aqui procurando por isso. Portanto, isso é para compartilhar o que eu achei uma solução viável, se você usar o Angular.

Na minha experiência, esse problema surge quando você adiciona a funcionalidade de queda de arquivo a uma página. Portanto, minha opinião é que o componente que adiciona isso também deve ser responsável por impedir a queda fora da zona de queda.

Na minha solução, a zona de recebimento é uma entrada com uma classe, mas qualquer seletor 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';
    }
  }
}

Os ouvintes são adicionados / removidos automaticamente quando o componente é criado / destruído, e outros componentes que usam a mesma estratégia na mesma página não interferem entre si devido ao stopPropagation ().

Superole
fonte
Isso funciona como um encanto !! O navegador até muda o cursor do mouse adicionando um ícone de proibição que é ótimo!
pti_jul 6/02
1

Para desenvolver o método "verifique o destino" descrito em algumas outras respostas, aqui está um método mais genérico / funcional:

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

Chamado 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
fonte
Além disso, pode adicionar um pouco de ES6 aqui: function preventDefaultExcept(...predicates){}. E então use-o comopreventDefaultExcept(isDropzone, isntParagraph)
hlfrmn 16/08/19
0

Eu tenho um HTML object( embed) que preenche a largura e a altura da página. A resposta do @ digital-plane funciona em páginas da Web normais, mas não se o usuário cair em um objeto incorporado. Então, eu precisava de uma solução diferente.

Se passarmos a usar a fase de captura de eventos , podemos obter os eventos antes que o objeto incorporado os receba (observe o truevalor no final da chamada do ouvinte de evento):

// 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 o código a seguir (com base na resposta do @ digital-plane), a página se torna um alvo de arrastar, impede que as incorporações de objetos capturem os eventos e, em seguida, carrega nossas imagens:

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

Testado no Firefox no Mac.

1,21 gigawatts
fonte
0

Como estou usando um seletor de classe para várias áreas de upload, minha solução assumiu essa forma menos pura

Baseado na resposta de Axel Amthor, com dependência do jQuery (com alias de $)

_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
fonte