Prevent browser from loading a drag-and-dropped file

JavascriptJqueryHtmlDrag and-Drop

Javascript Problem Overview


I'm adding an html5 drag and drop uploader to my page.

When a file is dropped into the upload area, everything works great.

However, if I accidentally drop the file outside of the upload area, the browser loads the local file as if it is a new page.

How can I prevent this behavior?

Thanks!

Javascript Solutions


Solution 1 - Javascript

You can add a event listener to the window that calls preventDefault() on all dragover and drop events.
Example:

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

Solution 2 - Javascript

After a lot of fiddling around, I found this to be the stablest solution:

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>

Setting both effectAllow and dropEffect unconditionally on the window causes my drop zone not to accept any d-n-d any longer, regardless whether the properties are set new or not.

Solution 3 - Javascript

To allow drag-and-drop only on some elements, you could do something like:

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

Solution 4 - Javascript

For jQuery the correct answer will be:

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

Here return false will behave as event.preventDefault() and event.stopPropagation().

Solution 5 - Javascript

> Note: Although the OP did not ask for an Angular solution, I came here looking for that. So this is to share what I found to be a viable solution, if you use Angular.

In my experience this problem first arises when you add file drop functionality to a page. Therefore my opinion is that the component that adds this, should also be responsible for preventing drop outside of the drop zone.

In my solution the drop zone is an input with a class, but any unambiguous selector works.

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

The listeners are added/removed automatically when component is created/destroyed, and other components using the same strategy on the same page do not interfere with each other due to the stopPropagation().

Solution 6 - Javascript

try this:

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

Solution 7 - Javascript

Preventing all drag and drop operations by default might not be what you want. It's possible to check if the drag source is an external file, at least in some browsers. I've included a function to check if the drag source is an external file in this StackOverflow answer.

Modifying Digital Plane's answer, you could do something like this:

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

Solution 8 - Javascript

To build on the "check the target" method outlined in a few other answers, here is a more generic/functional method:

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

Called like:

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

Solution 9 - Javascript

I have an HTML object (embed) that fills the width and height of the page. The answer by @digital-plane works on normal web pages but not if the user drops onto an embedded object. So I needed a different solution.

If we switch to using the event capture phase we can get the events before the embedded object receives them (notice the true value at the end of the event listener call):

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

Using the following code (based on @digital-plane's answer) the page becomes a drag target, it prevents object embeds from capturing the events and then loads our images:

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

Tested on Firefox on Mac.

Solution 10 - Javascript

I am using a class selector for multiple upload areas so my solution took this less pure form

Based on Axel Amthor's answer, with dependency on jQuery (aliased to $)

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTravisView Question on Stackoverflow
Solution 1 - JavascriptDigital PlaneView Answer on Stackoverflow
Solution 2 - JavascriptAxel AmthorView Answer on Stackoverflow
Solution 3 - Javascriptenthus1astView Answer on Stackoverflow
Solution 4 - JavascriptVisioNView Answer on Stackoverflow
Solution 5 - JavascriptSuperoleView Answer on Stackoverflow
Solution 6 - JavascriptmoeView Answer on Stackoverflow
Solution 7 - JavascriptShannon MatthewsView Answer on Stackoverflow
Solution 8 - Javascriptscott_trinhView Answer on Stackoverflow
Solution 9 - Javascript1.21 gigawattsView Answer on Stackoverflow
Solution 10 - Javascripthngr18View Answer on Stackoverflow