12

I want to overlay a div over the viewport when the user drags a file onto the window.

However, I'm having trouble with the event propagation. When I set the overlay to display: block it appears to fire off a dragleave event and then another dragenter and then another dragleave again, so it's always in a post-dragleave state. Of course I call e.stopPropagation() and e.preventDefault() on the event object, but it doesn't seem to make a difference.

The console.log() output when you drag something over the window:

dragenter
dragenter
dragleave
dragenter
dragleave

The css. #overlay is set to display: none by default, but will show if body has the dragenter class:

    body {
        position: absolute;
        height: auto;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: 0;
        padding: 0;
    }

    #overlay {
        position: absolute;        
        height: auto;
        width: auto;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: url(bg.png) repeat-x top right, url(bg.png) repeat-x bottom left, url(bg.png) repeat-y top right, url(bg.p
ng) repeat-y bottom left;
        display: none;
    }

    body.dragenter #overlay {
        display: block;
    }

The JavaScript; add the 'dragenter' class on dragenter and removes it on dragleave:

$(document).on('dragenter', function (e) {
    e.stopPropagation();
    e.preventDefault();
    console.log('dragenter');
    $(document.body).addClass('dragenter');
});

$(document).on('dragleave', function (e) {
    e.stopPropagation();
    e.preventDefault();
    console.log('dragleave';
    $(document.body).removeClass('dragenter');
});

The html:

<body>
<div id="overlay">...</div>
...    
</body>
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
ʞɔıu
  • 47,148
  • 35
  • 106
  • 149

4 Answers4

6

Your overlay takes up the whole document size, when you drag in, it fills up its space and your mouse is effectively taken out of the body and is now over the overlay. This triggers a mouseleave/mouseenter loop. To achieve what you are after, you may want to bind the event to a transparent overlay with a high z-index over the visible overlay which has a lower z-index. This would keep the event in the highest element.

Example:

http://jsfiddle.net/scottux/z7yaB/

Scottux
  • 1,577
  • 1
  • 11
  • 22
1

Thanks to Scottux, that led me onto the right track.

Only problem was it also covered up the rest of the page, so none of the elements or inputs were clickable. I had to hide #dragOverlay by default with "display: none" and display it on this event

// Display an overlay when dragging a file over
$('*:visible').live('dragenter', function(e) {
    e.stopPropagation();
    $('body').addClass('drag-enter');
});
twig
  • 4,034
  • 5
  • 37
  • 47
  • Awesome, exactly the problem i was looking to solve for a couple of hours now ;) but as far is i know jquery, $('*:visible').live() must be a pretty expensive selector. might there be another solution? i mean the guys at imgur.com obviously found a way (drag a file from your finder/explorer onto the site and you'll see) but i couldn't reverse engineer the code to find out how they did it :( – Philip Paetz Feb 09 '12 at 08:26
  • I think it's a rather heavy handed solution also, but it's the easiest one I find (with time constraints on a work project and all). I've tried looking at the imgur code too and it's not something I wanted to dig deeply into. – twig Feb 12 '12 at 01:09
0
    var dropZone = function() {
        var self = this;
        this.eTimestamp = 0;
        this.showDropZone = function(e) {
            e.stopPropagation();
            e.preventDefault();
            if (self.eTimestamp + 300 < e.timeStamp) {
                $("#coverDropZone").show();
                self.eTimestamp = e.timeStamp;
            }
            return false;
        }
        this.hideDropZone = function(e) {
            e.stopPropagation();
            e.preventDefault();
            if (self.eTimestamp + 300 < e.timeStamp) {
                $("#coverDropZone").hide();
                self.eTimestamp = e.timeStamp;
            }
            return false;
        }
        this.showImage = function(e) {
            e.stopPropagation();
            e.preventDefault();
            console.log(e);
            return false;
        }
        document.addEventListener('dragenter', self.showDropZone, false);
        document.addEventListener('dragleave', self.hideDropZone, false);
        document.addEventListener('drop', self.showImage, false);
    }
D.Dobchev
  • 9
  • 1
  • 4
  • 2
    Could you please explain your answer? Posting code without elaboration can confuse others with the same issue. – Seth Aug 28 '14 at 16:18
  • This definitely needs some clarification. – ouflak Aug 28 '14 at 16:35
  • im using this code to show the drop-zone when the mouse is entering the window dragging a file. the object has a property .eTimestamp that contains the event.timeStamp and im checking if there are at least 300ms between both events to prevent multiple firing ( i had that problem ) – D.Dobchev Sep 01 '14 at 18:07
-1

The simple solution is instead of using dragenter use dragover

dragover This event is fired as the mouse is moved over an element when a drag is occurring. Much of the time, the operation that occurs during a listener will be the same as the dragenter event.

Darren Cato
  • 1,382
  • 16
  • 22
  • Except that you can't detect when it leaves the drop zone. – cdmckay Sep 20 '13 at 02:45
  • This also causes the code to change the visibility of the overlay to fire repeatedly, which may (or may not) have performance consequences. – ZachB May 02 '15 at 23:20