我正在添加一个html5拖放上传程序到我的页面。

当文件被放入上传区域时,一切都很正常。

但是,如果我不小心将文件放到了上传区域之外,浏览器就会像加载新页面一样加载本地文件。

我该如何防止这种行为?

谢谢!


您可以向窗口添加一个事件监听器,在所有拖放事件上调用preventDefault()。 例子:

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

试试这个:

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

默认情况下阻止所有拖放操作可能不是您想要的。至少在某些浏览器中,可以检查拖拽源是否为外部文件。我已经包含了一个函数来检查拖动源是否是这个StackOverflow答案中的外部文件。

修改Digital Plane的答案,你可以这样做:

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

为了只允许在某些元素上进行拖放,你可以这样做:

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

在反复折腾之后,我发现这是最稳定的解决方案:

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>

在窗口上无条件地设置effectAllow和dropEffect会导致我的drop zone不再接受任何d-n-d,无论属性是否设置为新的。


jQuery的正确答案将是:

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

这里返回false将表现为event.preventDefault()和event.stopPropagation()。


在其他一些回答中提到的“检查目标”方法的基础上,这里有一个更通用/实用的方法:

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

被称为:

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

我有一个HTML对象(嵌入),填充页面的宽度和高度。@digital-plane的答案适用于普通网页,但如果用户落在嵌入对象上就不行了。所以我需要一个不同的解决方案。

如果我们切换到使用事件捕获阶段,我们可以在嵌入对象接收到它们之前获得事件(注意事件监听器调用结束时的真值):

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

使用下面的代码(基于@digital-plane的答案),页面成为一个拖动目标,它阻止对象嵌入捕获事件,然后加载我们的图像:

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

在Mac上的Firefox上测试。


我使用类选择器为多个上传区域,所以我的解决方案采取了这种不那么纯粹的形式

基于Axel Amthor的答案,依赖jQuery(别名为$)

_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);
        });
    },

注意:尽管OP没有要求Angular的解决方案,但我来这里就是为了寻找它。所以我想分享一下我发现的一个可行的解决方案,如果你使用Angular的话。

根据我的经验,这个问题首先出现在向页面添加文件拖放功能时。因此,我的观点是,添加这个的组件,也应该负责防止删除区域之外的删除。

在我的解决方案中,拖放区域是一个带有类的输入,但任何明确的选择器都可以工作。

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

当创建/销毁组件时,侦听器会自动添加/删除,并且由于stopPropagation(),在同一页面上使用相同策略的其他组件不会相互干扰。


下面是一个使用ES6语法的更现代化的版本。

let dropzoneId = 'dropzone' const dragEventHandler = e => { if (e.target.id !== dropzoneId) { e.preventDefault e.dataTransfer.effectAllowed = 'none' e.dataTransfer.dropEffect = 'none' } } // window.addEventListener("dragenter", dragEventHandler, false) // window.addEventListener("dragover", dragEventHandler, false) // window.addEventListener("drop", dragEventHandler, false) ['dragenter', 'dragover', 'drop'].forEach(ev => window.addEventListener(ev, dragEventHandler, false)) <div id="dropzone">...</div>


为了它的价值,我使用以下。如果不是特别优雅的话,可能会很好,很明确?

var myDropZone = document.getElementById('drop_zone');

// first, inhibit the default behaviour throughout the window
window.addEventListener('drop', () => { 
  event.preventDefault(); 
} );
window.addEventListener('dragover', () => { 
  event.dataTransfer.dropEffect = 'none'; // dont allow drops
  event.preventDefault(); 
} );

// Next, allow the cursor to show 'copy' as it is dragged over 
// my drop zone but dont forget to stop the event propagating

myDropZone.addEventListener('dragover', () => { 
  event.dataTransfer.dropEffect = 'copy';
  event.stopPropagation(); // important !!
  event.preventDefault();
} );

// In my drop zone, deal with files as they are dropped
myDropZone.addEventListener('drop', myDropHandler);