Prevent browser from loading a drag-and-dropped file

Viewed 88839

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!

12 Answers

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().

Here's a little more modernized version of this answer using ES6 syntax.

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>

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

For what its worth, I use the following. Nice and explicit if not particularly elegant perhaps?

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