HTML5在悬浮子元素时触发dragleave

我遇到的问题是,一个元素的dragleave事件是在悬停该元素的子元素时触发的。而且,当再次悬停回父元素时,dragenter不会被触发。

我做了一个简化的小提琴:http://jsfiddle.net/pimvdb/HU6Mk/1/

HTML:

<div id="drag" draggable="true">drag me</div>


<hr>


<div id="drop">
drop here
<p>child</p>
parent
</div>

使用以下JavaScript:

$('#drop').bind({
dragenter: function() {
$(this).addClass('red');
},


dragleave: function() {
$(this).removeClass('red');
}
});


$('#drag').bind({
dragstart: function(e) {
e.allowedEffect = "copy";
e.setData("text/plain", "test");
}
});

它应该做的是在拖拽一些东西时,通过将drop div设置为红色来通知用户。这是有效的,但如果你拖到p子对象中,dragleave将被触发,而div不再是红色的。移动回drop div也不会使它再次变为红色。有必要完全移出drop div并再次拖回它以使其为红色。

是否有可能防止dragleave在拖拽到子元素时触发?

2017年更新: TL;DR,查找CSS pointer-events: none;,如@ h.d.所述。在现代浏览器和IE11中都能运行。

115331 次浏览

你可以从jQuery源代码中得到一点灵感,在Firefox中修复它:

dragleave: function(e) {
var related = e.relatedTarget,
inside = false;


if (related !== this) {


if (related) {
inside = jQuery.contains(this, related);
}


if (!inside) {


$(this).removeClass('red');
}
}


}

不幸的是,它在Chrome中不起作用,因为relatedTarget似乎不存在于dragleave事件上,我假设你在Chrome中工作,因为你的例子在Firefox中不起作用。这是一个版本与上述代码实现。

问题是当鼠标移到子元素前面时,dragleave事件被触发。

我尝试了各种方法来检查e.target元素是否与this元素相同,但无法得到任何改进。

我修复这个问题的方式有点hack,但工作100%。

dragleave: function(e) {
// Get the location on screen of the element.
var rect = this.getBoundingClientRect();


// Check the mouseEvent coordinates are outside of the rectangle
if(e.x > rect.left + rect.width || e.x < rect.left
|| e.y > rect.top + rect.height || e.y < rect.top) {
$(this).removeClass('red');
}
}

不确定这是否跨浏览器,但我在Chrome中测试,它解决了我的问题:

我想拖放一个文件在整个页面,但我的dragleave被解雇时,我拖过子元素。我的解决方法是查看鼠标的x和y:

我有一个div覆盖我的整个页面,当页面加载我隐藏它。

当你拖拽文件时,我显示它,当你拖拽到父文件时,它处理它,当你离开父文件时,我检查x和y。

$('#draganddrop-wrapper').hide();


$(document).bind('dragenter', function(event) {
$('#draganddrop-wrapper').fadeIn(500);
return false;
});


$("#draganddrop-wrapper").bind('dragover', function(event) {
return false;
}).bind('dragleave', function(event) {
if( window.event.pageX == 0 || window.event.pageY == 0 ) {
$(this).fadeOut(500);
return false;
}
}).bind('drop', function(event) {
handleDrop(event);


$(this).fadeOut(500);
return false;
});

下面是Chrome的解决方案:

.bind('dragleave', function(event) {
var rect = this.getBoundingClientRect();
var getXY = function getCursorPosition(event) {
var x, y;


if (typeof event.clientX === 'undefined') {
// try touch screen
x = event.pageX + document.documentElement.scrollLeft;
y = event.pageY + document.documentElement.scrollTop;
} else {
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}


return { x: x, y : y };
};


var e = getXY(event.originalEvent);


// Check the mouseEvent coordinates are outside of the rectangle
if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) {
console.log('Drag is really out of area!');
}
})

下面是另一种基于事件时间的方法。

从子元素分派的dragenter事件可以被父元素捕获,并且它总是发生在dragleave之前。这两个事件之间的时间间隔非常短,比任何可能的人类鼠标操作都要短。因此,这个想法是记住dragenter事件发生的时间,并过滤在…之后“不太快”发生的dragleave事件。

这个简短的例子适用于Chrome和Firefox:

var node = document.getElementById('someNodeId'),
on   = function(elem, evt, fn) { elem.addEventListener(evt, fn, false) },
time = 0;


on(node, 'dragenter', function(e) {
e.preventDefault();
time = (new Date).getTime();
// Drag start
})


on(node, 'dragleave', function(e) {
e.preventDefault();
if ((new Date).getTime() - time > 5) {
// Drag end
}
})

这里是最简单的跨浏览器解决方案(认真地说):

jsfiddle <——试着在盒子里拖一些文件

你可以这样做:

var dropZone= document.getElementById('box');
var dropMask = document.getElementById('drop-mask');


dropZone.addEventListener('dragover', drag_over, false);
dropMask.addEventListener('dragleave', drag_leave, false);
dropMask.addEventListener('drop', drag_drop, false);
简单地说,你在dropzone内创建了一个“mask”,宽度&高度继承,位置绝对,只会显示拖拽开始的时间 所以,在显示了蒙版之后,你可以通过附加其他的dragleave &

离开或掉落后,您只需再次隐藏面具。< br > 简单,无并发症。

(奥林匹克广播服务公司。: Greg Pettit的建议——你必须确保蒙版悬浮在整个盒子上,包括边框)

我也遇到过同样的问题,下面是我的解决方案——我认为比上面的要简单得多。我不确定它是否跨浏览器(可能取决于冒泡顺序)

为了简单起见,我将使用jQuery,但解决方案应该是框架独立的。

事件以任意一种方式生成父节点,如下所示:

<div class="parent">Parent <span>Child</span></div>

我们附加事件

el = $('.parent')
setHover = function(){ el.addClass('hovered') }
onEnter  = function(){ setTimeout(setHover, 1) }
onLeave  = function(){ el.removeClass('hovered') }
$('.parent').bind('dragenter', onEnter).bind('dragleave', onLeave)

差不多就是这样。:)它可以工作,因为即使onEnter在子上先于onLeave在父上触发,我们延迟它稍微颠倒顺序,所以类先被删除,然后在一毫秒后重新应用。

在花了这么多小时后,我得到的建议完全按照预期工作。我只想在文件被拖拽时提供提示,而文档拖拽,拖拽会在Chrome浏览器上引起痛苦的闪烁。

这就是我解决它的方法,也为用户提供了适当的提示。

$(document).on('dragstart dragenter dragover', function(event) {
// Only file drag-n-drops allowed, http://jsfiddle.net/guYWx/16/
if ($.inArray('Files', event.originalEvent.dataTransfer.types) > -1) {
// Needed to allow effectAllowed, dropEffect to take effect
event.stopPropagation();
// Needed to allow effectAllowed, dropEffect to take effect
event.preventDefault();


$('.dropzone').addClass('dropzone-hilight').show();     // Hilight the drop zone
dropZoneVisible= true;


// http://www.html5rocks.com/en/tutorials/dnd/basics/
// http://api.jquery.com/category/events/event-object/
event.originalEvent.dataTransfer.effectAllowed= 'none';
event.originalEvent.dataTransfer.dropEffect= 'none';


// .dropzone .message
if($(event.target).hasClass('dropzone') || $(event.target).hasClass('message')) {
event.originalEvent.dataTransfer.effectAllowed= 'copyMove';
event.originalEvent.dataTransfer.dropEffect= 'move';
}
}
}).on('drop dragleave dragend', function (event) {
dropZoneVisible= false;


clearTimeout(dropZoneTimer);
dropZoneTimer= setTimeout( function(){
if( !dropZoneVisible ) {
$('.dropzone').hide().removeClass('dropzone-hilight');
}
}, dropZoneHideDelay); // dropZoneHideDelay= 70, but anything above 50 is better
});

另一种工作解决方案,稍微简单一点。

//Note: Due to a bug with Chrome the 'dragleave' event is fired when hovering the dropzone, then
//      we must check the mouse coordinates to be sure that the event was fired only when
//      leaving the window.
//Facts:
//  - [Firefox/IE] e.originalEvent.clientX < 0 when the mouse is outside the window
//  - [Firefox/IE] e.originalEvent.clientY < 0 when the mouse is outside the window
//  - [Chrome/Opera] e.originalEvent.clientX == 0 when the mouse is outside the window
//  - [Chrome/Opera] e.originalEvent.clientY == 0 when the mouse is outside the window
//  - [Opera(12.14)] e.originalEvent.clientX and e.originalEvent.clientY never get
//                   zeroed if the mouse leaves the windows too quickly.
if (e.originalEvent.clientX <= 0 || e.originalEvent.clientY <= 0) {

解决这个问题的“正确”方法是禁用拖放目标的子元素上的指针事件(如@ h.d.。的回答)。下面是我创建的演示这种技术的jsFiddle。不幸的是,这在IE11之前的Internet Explorer版本中不起作用,因为它们不支持指针事件

幸运的是,我能够想出一个解决办法,工作在旧版本的IE。基本上,它涉及识别和忽略在拖动子元素时发生的dragleave事件。因为dragenter事件是在父节点上的dragleave事件之前在子节点上触发的,所以可以向每个子节点添加单独的事件侦听器,这些子节点从拖放目标中添加或删除一个"ignore-drag-leave"类。然后,drop目标的dragleave事件监听器可以简单地忽略该类存在时发生的调用。这是一个jsFiddle演示了这个解决方法。它经过测试,可以在Chrome, Firefox和IE8+中运行。

更新:

我使用特性检测创建了演示组合解决方案的jsFiddle,在支持的情况下使用指针事件(目前是Chrome、Firefox和IE11),如果指针事件支持不可用,浏览器会退回到向子节点添加事件(IE8-10)。

是否有可能阻止dragleave在拖动到子元素时发射?

是的。

#drop * {pointer-events: none;}

CSS似乎足够Chrome。

当与Firefox一起使用它时,#drop不应该直接有文本节点(否则会有一个奇怪的元素“leave it to itself”的问题),所以我建议只留下一个元素(例如,在#drop中使用div来放置所有内容)

这是jsfiddle求解原问题(坏的)例子

我还从@Theodore Brown的例子中派生了一个简化版本,但只基于这个CSS。

虽然不是所有的浏览器都实现了这个CSS: http://caniuse.com/pointer-events < / p >

在Facebook源代码中,我可以多次找到这个pointer-events: none;,但是它可能与优雅的降级回退一起使用。至少它是如此简单,解决了许多环境中的问题。

我写了一个名为彷徨的小库来处理这个确切的问题,除了在IE中默默不做任何事情外,它可以在任何地方工作(它不支持DOM事件构造函数,但使用jQuery的自定义事件编写类似的东西非常容易)

pimvdb . .

你为什么不试试用下降代替dragleave呢?这对我很管用。希望这能解决你的问题。

请检查jsFiddle: http://jsfiddle.net/HU6Mk/118/

$('#drop').bind({
dragenter: function() {
$(this).addClass('red');
},


drop: function() {
$(this).removeClass('red');
}
});


$('#drag').bind({
dragstart: function(e) {
e.allowedEffect = "copy";
e.setData("text/plain", "test");
}
});

你只需要保留一个引用计数器,当你得到dragenter时增加它,当你得到dragleave时减少它。当计数器为0时-删除类。

var counter = 0;


$('#drop').bind({
dragenter: function(ev) {
ev.preventDefault(); // needed for IE
counter++;
$(this).addClass('red');
},


dragleave: function() {
counter--;
if (counter === 0) {
$(this).removeClass('red');
}
}
});

注意:在drop事件中,将计数器重置为零,并清除添加的类。

你可以运行它在这里

我也有同样的问题,并试图使用pk7s的解决方案。它工作,但它可以做得更好一点,没有任何额外的dom元素。

基本上想法是一样的-在可掉落的区域上添加一个额外的不可见的覆盖。让我们在没有任何额外dom元素的情况下这样做。下面是CSS伪元素开始发挥作用的部分。

Javascript

var dragOver = function (e) {
e.preventDefault();
this.classList.add('overlay');
};


var dragLeave = function (e) {
this.classList.remove('overlay');
};




var dragDrop = function (e) {
this.classList.remove('overlay');
window.alert('Dropped');
};


var dropArea = document.getElementById('box');


dropArea.addEventListener('dragover', dragOver, false);
dropArea.addEventListener('dragleave', dragLeave, false);
dropArea.addEventListener('drop', dragDrop, false);

CSS

这个after规则将为可掉落区域创建一个完全覆盖的覆盖层。

#box.overlay:after {
content:'';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 1;
}

下面是完整的解决方案:http://jsfiddle.net/F6GDq/8/

我希望它能帮助有同样问题的人。

你可以使用带有transitioning标志的超时并监听顶部元素。子事件的dragenter / dragleave会冒泡到容器中。

由于子元素的dragenter在容器的dragleave之前触发,我们将把标志显示设置为过渡1ms…dragleave监听器将在1ms结束前检查标志。

该标志仅在转换到子元素时为真,而在转换到(容器的)父元素时为假。

var $el = $('#drop-container'),
transitioning = false;


$el.on('dragenter', function(e) {


// temporarily set the transitioning flag for 1 ms
transitioning = true;
setTimeout(function() {
transitioning = false;
}, 1);


$el.toggleClass('dragging', true);


e.preventDefault();
e.stopPropagation();
});


// dragleave fires immediately after dragenter, before 1ms timeout
$el.on('dragleave', function(e) {


// check for transitioning flag to determine if were transitioning to a child element
// if not transitioning, we are leaving the container element
if (transitioning === false) {
$el.toggleClass('dragging', false);
}


e.preventDefault();
e.stopPropagation();
});


// to allow drop event listener to work
$el.on('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
});


$el.on('drop', function(e) {
alert("drop!");
});

jsfiddle: http://jsfiddle.net/ilovett/U7mJj/

在这个问题被提出之后已经有一段时间了,并且提供了很多解决方案(包括丑陋的黑客)。

我设法解决了我最近遇到的同样的问题,这要感谢这个< >强回答< / >强中的答案,并认为它可能对来到本页的人有帮助。 整个思想是每次在父元素或子元素上调用evenet.target时,将evenet.target存储在ondrageenter中。然后在ondragleave中检查当前目标(event.target)是否等于你存储在ondragenter中的对象

这两者匹配的唯一情况是当您的拖动离开浏览器窗口时。

这样工作正常的原因是,当鼠标离开一个元素(比如el1)并进入另一个元素(比如el2)时,首先调用el2.ondragenter,然后调用el1.ondragleave。只有当拖动离开/进入浏览器窗口时,event.targetel2.ondragenterel1.ondragleave中将为''

这是我的工作样本。我已经在IE9+、Chrome、Firefox和Safari上进行了测试。

(function() {
var bodyEl = document.body;
var flupDiv = document.getElementById('file-drop-area');


flupDiv.onclick = function(event){
console.log('HEy! some one clicked me!');
};


var enterTarget = null;


document.ondragenter = function(event) {
console.log('on drag enter: ' + event.target.id);
enterTarget = event.target;
event.stopPropagation();
event.preventDefault();
flupDiv.className = 'flup-drag-on-top';
return false;
};


document.ondragleave = function(event) {
console.log('on drag leave: currentTarget: ' + event.target.id + ', old target: ' + enterTarget.id);
//Only if the two target are equal it means the drag has left the window
if (enterTarget == event.target){
event.stopPropagation();
event.preventDefault();
flupDiv.className = 'flup-no-drag';
}
};
document.ondrop = function(event) {
console.log('on drop: ' + event.target.id);
event.stopPropagation();
event.preventDefault();
flupDiv.className = 'flup-no-drag';
return false;
};
})();

这是一个简单的html页面:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Multiple File Uploader</title>
<link rel="stylesheet" href="my.css" />
</head>
<body id="bodyDiv">
<div id="cntnr" class="flup-container">
<div id="file-drop-area" class="flup-no-drag">blah blah</div>
</div>
<script src="my.js"></script>
</body>
</html>

使用适当的样式,我所做的是使内部div (#file-drop area)在文件被拖到屏幕时变得更大,以便用户可以轻松地将文件放到适当的位置。

我有一个类似的问题-我的代码隐藏dropzone dragleave事件的身体被触发时悬停子元素使dropzone闪烁在谷歌Chrome。

我能够通过调度隐藏dropzone的函数来解决这个问题,而不是立即调用它。然后,如果触发另一个拖拽或拖拽,则取消预定的函数调用。

body.addEventListener('dragover', function() {
clearTimeout(body_dragleave_timeout);
show_dropzone();
}, false);


body.addEventListener('dragleave', function() {
clearTimeout(body_dragleave_timeout);
body_dragleave_timeout = setTimeout(show_upload_form, 100);
}, false);


dropzone.addEventListener('dragover', function(event) {
event.preventDefault();
dropzone.addClass("hover");
}, false);


dropzone.addEventListener('dragleave', function(event) {
dropzone.removeClass("hover");
}, false);

您需要删除拖动目标的所有子对象的指针事件。

function disableChildPointerEvents(targetObj) {
var cList = parentObj.childNodes
for (i = 0; i < cList.length; ++i) {
try{
cList[i].style.pointerEvents = 'none'
if (cList[i].hasChildNodes()) disableChildPointerEvents(cList[i])
} catch (err) {
//
}
}
}

下面是另一个使用document.elementFromPoint的解决方案:

 dragleave: function(event) {
var event = event.originalEvent || event;
var newElement = document.elementFromPoint(event.pageX, event.pageY);
if (!this.contains(newElement)) {
$(this).removeClass('red');
}
}

希望这是一个小提琴

使用代码http://jsfiddle.net/HU6Mk/258/:

$('#drop').bind({
dragenter: function() {
$(this).addClass('red');
},


dragleave: function(event) {
var x = event.clientX, y = event.clientY,
elementMouseIsOver = document.elementFromPoint(x, y);
if(!$(elementMouseIsOver).closest('.red').length) {
$(this).removeClass('red');
}
}
});

试着使用event。eventphase。只有输入目标时,它才会被设置为2 (Event.AT_TARGET),否则它会被设置为3 (Event.BUBBLING_PHASE)。

我已经使用eventPhase来绑定或取消绑定dragleave事件。

$('.dropzone').on('dragenter', function(e) {


if(e.eventPhase === Event.AT_TARGET) {


$('.dropzone').addClass('drag-over');


$('.dropzone').on('dragleave', function(e) {
$('.dropzone').removeClass('drag-over');
});


}else{


$('.dropzone').off('dragleave');


}
})

圭多

我写了一个名为雨滴的拖放模块来修复这个奇怪的行为。如果你正在寻找一个好的低级拖放模块,你可以用作任何事情的基础(文件上传,应用程序内拖放,从或从外部源拖放),你应该检查这个模块:

https://github.com/fresheneesz/drip-drop

这是你在点滴中尝试做的事情:

$('#drop').each(function(node) {
dripDrop.drop(node, {
enter: function() {
$(node).addClass('red')
},
leave: function() {
$(node).removeClass('red')
}
})
})
$('#drag').each(function(node) {
dripDrop.drag(node, {
start: function(setData) {
setData("text", "test") // if you're gonna do text, just do 'text' so its compatible with IE's awful and restrictive API
return "copy"
},
leave: function() {
$(node).removeClass('red')
}
})
})

为了在没有库的情况下做到这一点,我在滴漏中使用了反制技术,尽管评分最高的答案错过了重要的步骤,这将导致除了第一个滴漏之外的所有东西都被打破。以下是正确的做法:

var counter = 0;
$('#drop').bind({
dragenter: function(ev) {
ev.preventDefault()
counter++
if(counter === 1) {
$(this).addClass('red')
}
},


dragleave: function() {
counter--
if (counter === 0) {
$(this).removeClass('red');
}
},
drop: function() {
counter = 0 // reset because a dragleave won't happen in this case
}
});

如果你使用的是HTML5,你可以得到父类的clientRect:

let rect = document.getElementById("drag").getBoundingClientRect();

然后在parent.dragleave()中:

dragleave(e) {
if(e.clientY < rect.top || e.clientY >= rect.bottom || e.clientX < rect.left || e.clientX >= rect.right) {
//real leave
}
}

这里是一个jsfiddle

一个非常简单的解决方案是使用pointer-events CSS属性。只要在每个子元素的dragstart上设置它的值为none。这些元素将不再触发与鼠标相关的事件,因此它们不会捕捉鼠标在它们上面,因此不会触发父元素上的dragleave

不要忘记在完成拖动时将此属性设置回auto;)

只要检查拖过的元素是否是子元素,如果是,那么不要删除'拖过'样式的类。非常简单,对我来说很有用:

 $yourElement.on('dragleave dragend drop', function(e) {
if(!$yourElement.has(e.target).length){
$yourElement.removeClass('is-dragover');
}
})

我找到了一个与@azlar的答案相似但更优雅的解决方案,这是我的解决方案:

$(document).on({
dragenter: function(e) {
e.stopPropagation();
e.preventDefault();
$("#dragging").show();
},
dragover: function(e) {
e.stopPropagation();
e.preventDefault();
},
dragleave: function(e) {
e.stopPropagation();
e.preventDefault();
if (e.clientX <= 0 ||
// compare clientX with the width of browser viewport
e.clientX >= $(window).width() ||
e.clientY <= 0 ||
e.clientY >= $(window).height())
$("#dragging").hide();
}
});

这个方法检测鼠标是否离开了页面。

一个简单的解决方案是将css规则pointer-events: none添加到子组件中,以防止触发ondragleave。看到的例子:

function enter(event) {
document.querySelector('div').style.border = '1px dashed blue';
}


function leave(event) {
document.querySelector('div').style.border = '';
}
div {
border: 1px dashed silver;
padding: 16px;
margin: 8px;
}


article {
border: 1px solid silver;
padding: 8px;
margin: 8px;
}


p {
pointer-events: none;
background: whitesmoke;
}
<article draggable="true">drag me</article>


<div ondragenter="enter(event)" ondragleave="leave(event)">
drop here
<p>child not triggering dragleave</p>
</div>

"dragleave"事件在鼠标指针退出目标容器的拖拽区域时触发。

这很有意义,因为在许多情况下,只有父对象可能是可抛弃的,而不是子对象。 我认为event.stopPropogation ()应该处理这种情况,但似乎它没有做到这一点

上面提到的一些解决方案似乎对大多数情况下都有效,但在那些不支持dragenter / dragleave事件的情况下失败,比如iframe

一种解决方法是检查event.relatedTarget并验证它是否位于容器内,然后像我在这里所做的那样忽略dragleave事件:

function isAncestor(node, target) {
if (node === target) return false;
while(node.parentNode) {
if (node.parentNode === target)
return true;
node=node.parentNode;
}
return false;
}


var container = document.getElementById("dropbox");
container.addEventListener("dragenter", function() {
container.classList.add("dragging");
});


container.addEventListener("dragleave", function(e) {
if (!isAncestor(e.relatedTarget, container))
container.classList.remove("dragging");
});

你可以找到一个工作小提琴在这里!

到目前为止,这个相当简单的解决方案对我来说是有效的,假设您的事件分别附加到每个拖动元素。

if (evt.currentTarget.contains(evt.relatedTarget)) {
return;
}

非常简单的解决方案:

parent.addEventListener('dragleave', function(evt) {
if (!parent.contains(evt.relatedTarget)) {
// Here it is only dragleave on the parent
}
}

解决. . !

为ex声明任意数组:

targetCollection : any[]


dragenter: function(e) {
this.targetCollection.push(e.target); // For each dragEnter we are adding the target to targetCollection
$(this).addClass('red');
},


dragleave: function() {
this.targetCollection.pop(); // For every dragLeave we will pop the previous target from targetCollection
if(this.targetCollection.length == 0) // When the collection will get empty we will remove class red
$(this).removeClass('red');
}

不需要担心子元素。

这是我的解决方案(https://jsfiddle.net/42mh0fd5/8):

<div id="droppable">
<div id="overlay"></div>
<a href="">test child 1</a>
<br /><br />
<button>test child 2</button>
<br /><br />
<button>test child 3</button>
<br />
</div>
<p id="draggable" draggable="true">This element is draggable.</p>




<script type="text/javascript">
var dropElem = document.getElementById('droppable');
var overlayElem = document.getElementById('overlay');


overlayElem.addEventListener('drop', function(ev) {
ev.preventDefault();
console.log('drop', ev.dataTransfer.files)
overlayElem.classList.remove('dragover')
dropElem.classList.add('dropped')
console.log('drop')
}, false);


overlayElem.addEventListener('dragover', function(ev) {
ev.preventDefault();
}, false);


overlayElem.addEventListener('dragleave', function(ev) {
console.log('dragleave')
overlayElem.classList.remove('dragover')
}, false);


dropElem.addEventListener('dragenter', function(ev) {
console.log('dragenter')
overlayElem.classList.add('dragover')
}, false);
</script>


<style>
#draggable{
padding:5px;
background: #fec;
display: inline-block;
}
#droppable{
width: 300px;
background: #eef;
border: 1px solid #ccd;
position: relative;
text-align: center;
padding:30px;
}
#droppable.dropped{
background: #fee;
}
#overlay.dragover{
content:"";
position: absolute;
z-index: 1;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,.2);
border:3px dashed #999;
}
</style>

我在这个问题上挣扎了很多,即使在读完所有这些答案之后,我想我可以和你分享我的解决方案,因为我认为这可能是一种更简单的方法,尽管有点不同。我的想法是简单地完全省略dragleave事件监听器,并在每个新的dragenter事件被触发时编码dragleave行为,同时确保dragenter事件不会被不必要地触发。

在下面的例子中,我有一个表,我希望能够通过拖动和amp交换表的行内容;下降的API。在dragenter上,一个CSS类将被添加到当前拖动元素的row元素中,以突出显示它,而在dragleave上,这个类将被删除。

例子:

非常基本的HTML表格:

<table>
<tr>
<td draggable="true" class="table-cell">Hello</td>
</tr>
<tr>
<td draggable="true" clas="table-cell">There</td>
</tr>
</table>

dragenter事件处理函数,添加到每个表格单元格中(除了dragstartdragoverdropdragend处理程序,这些处理程序不是针对这个问题的,所以这里不复制):

/*##############################################################################
##                              Dragenter Handler                             ##
##############################################################################*/


// When dragging over the text node of a table cell (the text in a table cell),
// while previously being over the table cell element, the dragleave event gets
// fired, which stops the highlighting of the currently dragged cell. To avoid
// this problem and any coding around to fight it, everything has been
// programmed with the dragenter event handler only; no more dragleave needed


// For the dragenter event, e.target corresponds to the element into which the
// drag enters. This fact has been used to program the code as follows:


var previousRow = null;


function handleDragEnter(e) {
// Assure that dragenter code is only executed when entering an element (and
// for example not when entering a text node)
if (e.target.nodeType === 1) {
// Get the currently entered row
let currentRow = this.closest('tr');
// Check if the currently entered row is different from the row entered via
// the last drag
if (previousRow !== null) {
if (currentRow !== previousRow) {
// If so, remove the class responsible for highlighting it via CSS from
// it
previousRow.className = "";
}
}
// Each time an HTML element is entered, add the class responsible for
// highlighting it via CSS onto its containing row (or onto itself, if row)
currentRow.className = "ready-for-drop";
// To know which row has been the last one entered when this function will
// be called again, assign the previousRow variable of the global scope onto
// the currentRow from this function run
previousRow = currentRow;
}
}

在代码中留下非常基本的注释,因此这段代码也适合初学者。希望这对你有所帮助!请注意,您当然需要将我上面提到的所有事件侦听器添加到每个表单元格中,这样才能工作。

我找到了一个简单的解决方法,所以分享吧。这对我来说很有效。

试试看。

实际上,你只能通过dragenter事件来实现这一点,你甚至不需要注册dragleave。你所需要的就是在你的掉落区周围有一个不掉落的区域,就是这样。

你也可以有嵌套的dropzone,这是完美的。检查这个嵌套dropzones

$('.dropzone').on("dragenter", function(e) {
e.preventDefault();
e.stopPropagation();
$(this).addClass("over");
$(".over").not(this).removeClass("over"); // in case of multiple dropzones
});


$('.dropzone-leave').on("dragenter", function(e) {
e.preventDefault();
e.stopPropagation();
$(".over").removeClass("over");
});


// UPDATE
// As mar10 pointed out, the "Esc" key needs to be managed,
// the easiest approach is to detect the key and clean things up.


$(document).on('keyup', function(e){
if (e.key === "Escape") {
$(".over").removeClass("over");
}
});

我能够在dragleave上使用timeout来做到这一点。与此方法的其他答案不同,我认为在dragover上重置这个超时以避免闪烁是至关重要的。

我写的只是你需要的函数,然后使用你选择的框架进行绑定

let dragoverTimeout;
const onDragOver = (e: Event) => {
e.preventDefault();
e.stopPropagation();


if (dragoverTimeout) {
window.clearTimeout(dragoverTimeout);
dragoverTimeout = null;
}
// Add your class here
}


const onDragLeave = (e: Event) => {
e.preventDefault();
e.stopPropagation();


if (!dragoverTimeout) {
dragoverTimeout = window.setTimeout(() => {
// Remove your class here
}, 100);
}
}


const onDrop = (e) => {
e.preventDefault();
e.stopPropagation();


const files = e.dataTransfer.files;
// Remove your class here


if (files.length > 0) {
this.uploadFile(files);
}
}

接受的答案在某些情况下可能有效,但如果您有多个重叠的子元素,它将很快崩溃。

根据您的用例,本文的顶部答案可能更简单、更清晰

< a href = " https://stackoverflow.com/questions/10867506/dragleave-of-parent-element-fires-when-dragging-over-children-elements " > & # 39; dragleave& # 39;的父元素在拖动子元素时触发

我知道这是一个老问题,但我想补充我的偏好。我处理这个通过添加类触发css:后元素在更高的z-index然后你的内容。这样可以过滤掉所有的垃圾。

.droppable{
position: relative;
z-index: 500;
}


.droppable.drag-over:after{
content: "";
display:block;
position:absolute;
left:0;
right:0;
top:0;
bottom:0;
z-index: 600;
}

然后只需在您的第一个dragenter事件上添加拖拽类,并且不再有子元素触发该事件。

dragEnter(event){
dropElement.classList.add('drag-over');
}


dragLeave(event){
dropElement.classList.remove('drag-over');
}