I'm trying to create a file drag/drop handler (drag a file into the browser window, to be used for upload).
For some reason when I bind the drag/drop listener to $("body")
instead of to a $("div")
in the body the events fire several times in a row, sometimes even non-stop (seemingly looping). What could be causing this?
Here's a trimmed down version of the code: http://jsfiddle.net/WxMwK/9/
var over = false;
$("body")
.on("dragover", function(e){
e.preventDefault();
if (! over) {
over = true;
$("ul").append($("<li/>").text("dragover"));
}
})
.on("dragleave", function(e){
e.preventDefault();
if (over) {
over = false;
$("ul").append($("<li/>").text("dragleave"));
}
})
.on("drop", function(e){
e.preventDefault();
if (over) {
over = false;
$("ul").append($("<li/>").text("drop"));
}
});
To test: drag a file into the orange area, you'll see the event firing multiple times in a row.
The anon is (mostly) correct. To put it simply: when the mouse moves over the edge of an element inside your drop target, you get a dropenter
for the element under the cursor and a dropleave
for the element that was under the cursor previously. This happens for absolutely any descendant.
You can't check the element associated with dragleave
, because if you move the mouse from your drop target onto a child element, you'll get a dropenter
for the child and then a dropleave
for the target! It's kind of ridiculous and I don't see how this is a useful design at all.
Here's a crappy jQuery-based solution I came up with some time ago.
var $drop_target = $(document.body);
var within_enter = false;
$drop_target.bind('dragenter', function(evt) {
// Default behavior is to deny a drop, so this will allow it
evt.preventDefault();
within_enter = true;
setTimeout(function() { within_enter = false; }, 0);
// This is the part that makes the drop area light up
$(this).addClass('js-dropzone');
});
$drop_target.bind('dragover', function(evt) {
// Same as above
evt.preventDefault();
});
$drop_target.bind('dragleave', function(evt) {
if (! within_enter) {
// And this makes it un-light-up :)
$(this).removeClass('js-dropzone');
}
within_enter = false;
});
// Handle the actual drop effect
$drop_target.bind('drop', function(evt) {
// Be sure to reset your state down here
$(this).removeClass('js-dropzone');
within_enter = false;
evt.preventDefault();
do_whatever(evt.originalEvent.dataTransfer.files);
});
The trick relies on two facts:
dragenter
and dragleave
will be queued up for the target element—in that order.dragenter
and dragleave
are queued together.So here's what happens.
dragenter
event, I set some shared variable to indicate that the drag movement hasn't finished resolving yet.setTimeout
with a delay of zero to immediately change that variable back.dragleave
's event handler.dragleave
sees that it was paired with a dragenter
on the same target element, that means the mouse must have moved from some descendant to some other descendant. Otherwise, the mouse is actually leaving the target element.setTimeout
finally resolves zero seconds later, setting back the variable before another event can come along.I can't think of a simpler approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With