悬停子元素时触发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");
                 }
                });

要做的是div在拖曳某物时通过将其下拉为红色来通知用户这可行,但是如果您拖入p孩子,则将dragleave被发射,而div不再是红色。返回到下拉列表div也不会再次使其变为红色。有必要将其完全移出拖放div,然后再次拖动回拖放以使其变为红色。

Is it possible to prevent dragleave from firing when dragging into a child element?

2017 Update: TL;DR, Look up CSS pointer-events: none; as described in @H.D.'s answer below that works in modern browsers and IE11.

老丝2020/03/24 16:35:03

当鼠标指针离开目标容器的拖动区域时,将触发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");
});

您可以在这里找到有用的小提琴

宝儿理查德2020/03/24 16:35:03

我已经编写了一个名为Dragster的小库来处理这个确切的问题,它可以在IE之外无所事事地工作(它不支持DOM事件构造函数,但是使用jQuery的自定义事件编写类似的东西很容易),因此可以在任何地方使用。

樱小小小小2020/03/24 16:35:02

到目前为止,假设您的事件分别附加到每个拖动元素,那么这种对我来说仍然很有效的解决方案。

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}
猿乐小小2020/03/24 16:35:02

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

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

古一Mandy2020/03/24 16:35:02

如果您使用的是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

Mandy村村2020/03/24 16:35:02

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

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

更新:

我创建了一个jsFiddle,演示了使用功能检测的组合解决方案,其中使用了指针事件(如果支持的话)(当前支持Chrome,Firefox和IE11),并且如果没有指针事件支持(IE8,则浏览器会回退到向子节点添加事件) -10)。

西门2020/03/24 16:35:02

拖动到子元素中时,是否可以防止dragleave触发?

是。

#drop * {pointer-events: none;}

该CSS对于Chrome来说似乎足够了。

在Firefox中使用它时,#drop不应直接具有文本节点(否则会有一个奇怪的问题,其中一个元素“将其留给自己”),因此我建议仅将其保留一个元素(例如,在内部使用div #drop将所有内容放进去)

这是一个解决原始问题(破碎)示例的jsfiddle

我还从@Theodore Brown示例创建了简化版本,但仅基于此CSS。

不过,并非所有浏览器都实现了此CSS:http//caniuse.com/pointer-events

看到Facebook源代码,我可以pointer-events: none;多次找到它,但是它可能与优美的降级后备功能结合使用。至少它是如此简单,并且可以解决很多环境下的问题。