4

This is the behavior I want:

  1. The user starts dragging a file from the file explorer
  2. When the file hovers over the browser window 3 drop zones appear
  3. When the user cancels the drag and drop or drops the file the drop zones disappear.

The problem I'm having is with #3.
The drop zones appear fine when using the dragenter on document but I can't get them to disappear again.

I've tried binding on dragend which never fires, dragleave which fires every time the the drag leaves a descendant so the drag area blinks.

Which event would be the correct one to listen to?

Nicklas A.
  • 6,501
  • 7
  • 40
  • 65

6 Answers6

9

I haven't fully tested it, but it seems as though you could leverage the annoying behaviour of dragenter and dragleave which fires on every single element.

var counter = 0;
$(document).on('dragenter', function () { 
  if (counter++ === 0) {
    console.log('entered the page');
  }
});

$(document).on('dragleave', function () {
  if (--counter === 0) {
    console.log('left the page');
  }
});

Seems to also work if the drag is cancelled by pressing escape.

http://jsbin.com/atodem/2/

nickf
  • 537,072
  • 198
  • 649
  • 721
  • Don't forget to add on dragover to prevent default drag&drop, besides works exactly as I want it to! – FDIM Nov 12 '13 at 14:18
  • Be careful, because this breaks easily. Check this [fiddle](http://jsfiddle.net/ueLfnLz6/3/) that shows only one of the problems. Check [my answer](http://stackoverflow.com/a/29869208/636561) for an alternative. – Luca Fagioli Apr 26 '15 at 17:37
  • Works like a charm on Chrome and Firefox, but the counter sometimes goes wrong on IE 11. – Blaž Zupančič Jul 24 '16 at 16:27
7

On document, you want to listen to both dragleave and dragover, to hide and show the zones respectively.

Tanzeeb Khalili
  • 7,324
  • 2
  • 23
  • 27
1

Well, the dragstart and dragend events appear to only fire when dragging something from the browser to the browser, which is hardly useful in your case. I couldn't seem to stop the flicker, but if you add a timeout to the dragenter and dragleave events, you can minimize the flicker:

window.onload=function(){
    var drag = new (function(){
        var timeout;
        this.detected = function(){
            return !(timeout === undefined)
        }
        this.start = function(){
            clearTimeout(timeout);
        }
        function endDrag(){
            for (var i=0;i<3;i++) //no longer dragging, remove drop zones
                document.getElementsByClassName("dropZone")[i].style.display="none";
        }
        this.end = function(){
            timeout = setTimeout((function(){timeout=undefined;endDrag();}),1500)
        }
    })()
    document.body.ondragenter = function(){
        drag.start();
        for (var i=0;i<3;i++) //drag started, show drop zones
            document.getElementsByClassName("dropZone")[i].style.display="block";
    }
    document.body.ondragleave = function(event){
        event = event||window.event
        if ((event.source||event.target)==document.body)
            drag.end();
    }
}

Hope this helps, sorry if it isn't perfect. :-(

Zaq
  • 1,348
  • 1
  • 11
  • 19
0

You can verify it by checking if drop has been called before dragend.

     var dragConfirmed;

     document.addEventListener("drop", function( event ) {
          // prevent default action (open as link for some elements)
          event.preventDefault();

          dragConfirmed = true;
      }, false);



      document.addEventListener("dragend", function( event ) {

        if (dragConfirmed != true)
        {
             // Escape has been pressed
        }

      }, false);
0

dragleave didn't work for me. I used a timeout:

function stopDrag(){
   console.log('bye drag!');
}

var timeoutHandle;
document.addEventListener('dragover', function(){
    console.log('you are dragging');
    window.clearTimeout(timeoutHandle);
    timeoutHandle = window.setTimeout(stopDrag, 200);
}, false);
Aureliano Far Suau
  • 6,531
  • 2
  • 22
  • 25
0

None of the other answered worked for me. For my case, I needed to raise the drop-target z-index above other elements when dragging started, and lower it back after dragging ended. What I found was that when dragging was initiated, the mousemove event stopped firing. I used that behavior to detect when the drag actually stopped.

NOTE: only tested w/ firefox v104

let dragging = false;

function dragInit(event) {
    // add drag-init class to all drop targets, raising their z-index 
    if (dragging) return // prevent duplicate calls
    dragging = true
    for (const dt of document.querySelectorAll('.droptarget')) dt.classList.add('drag-init')
    // bind mousemove to body element; mouse move events are stopped while dragging 
    document.body.addEventListener('mousemove', dragEnd)
}

function dragEnd(event) {
    // when the dragging ends, the mousemove events are generated, remove the drag-init class to lower the 
    // droptargets back below the other elements 
    dragging = false
    // remove the listener, no longer needed
    document.body.removeEventListener('mousemove', dragEnd)
    for (const dt of document.querySelectorAll('.droptarget')) dt.classList.remove('drag-init')
}
// initialize drag and drop
document.body.addEventListener('dragenter', dragInit)

CSS

.droptarget {
    // start the drop target below other elements
    z-index: -1;
}

.droptarget.drag-init {
// user is currently dragging, raise the drop target above other elements 
    z-index: 100;
}
user2682863
  • 3,097
  • 1
  • 24
  • 38