Here's my code:
var dragStart = _.debounce(function () {
console.log("START");
$dropIndicator.appendTo(document.body).show();
}, 10, {
leading: true,
trailing: false
});
var dragStop = _.debounce(function () {
console.log("STOP");
$dropIndicator.hide();
}, 10, {
leading: false,
trailing: true
});
$(window)
.on('dragover', function (ev) {
ev.preventDefault();
ev.originalEvent.dataTransfer.dropEffect = 'copy';
dragStop.cancel();
dragStart();
})
.on('dragleave', function (ev) {
ev.preventDefault();
dragStop();
})
.on('drop', function (ev) {
ev.preventDefault();
dragStop();
uploadFiles(ev.originalEvent.dataTransfer.files);
});
If I drag a file over top of my window, I see my drop indicator as expected. However, as a move the file around in circles over the window (without every moving my mouse off the window), I see "STOP" is called periodically.
MDN says dragleave
shouldn't be called until "a dragged element or text selection leaves a valid drop target" -- but my target is the window, which I never left.
Why is it being called? This causes my drop indicator to flicker, or worse, sometimes the drop event isn't caught at all.
I put the debouncing in there to lessen the problem, but it's still an issue with or without debouncing.
I've tried a few different libraries, but none of them work particularly well. Here's the best solution I've come up with:
var $dropIndicator = $('#drop-indicator');
let dragOpen = false;
let dragElements = new Set();
let closeTimer = null;
function showDragIndicator() {
clearTimeout(closeTimer);
if(!dragOpen) {
$dropIndicator.appendTo(document.body).show();
dragOpen = true;
}
}
function hideDragIndicator() {
clearTimeout(closeTimer);
closeTimer = setTimeout(() => {
hideDragIndicatorNow();
}, 100);
}
function hideDragIndicatorNow() {
if(dragOpen) {
$dropIndicator.hide();
dragOpen = false;
dragElements.clear();
}
}
$(window)
.on('drop', function(ev) {
ev.preventDefault();
hideDragIndicator();
uploadFiles(ev.originalEvent.dataTransfer.files);
})
.on('dragover', function(ev) {
ev.preventDefault();
showDragIndicator();
})
.on('dragenter', function(ev) {
ev.preventDefault();
dragElements.add(ev.target);
showDragIndicator();
})
.on('dragleave', function(ev) {
ev.preventDefault();
dragElements.delete(ev.target);
if(dragElements.size === 0) {
hideDragIndicator();
}
})
.on('mousemove', function(ev) {
ev.preventDefault();
hideDragIndicatorNow();
});
Works pretty well in Firefox and Chrome for Ubuntu and Windows and IE11 on Windows.
Firefox Windows has a bug where the dragleave
event won't fire when you drag a file over the window and then back off again which is why I've added a mousemove
event so that the drop indicator will at least clear when you move your mouse back onto the Window.
The hide delay was necessary to prevent some flicker even though I've tracked all the elements that trigger the enter/leave they seem to fire too much.
You must bind a dragover
event for it to work in IE.