悬停子元素时触发 HTML5 dragleave

IT技术 javascript jquery html drag-and-drop jquery-events
2021-01-23 11:12:09

我遇到的问题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并再次拖回水滴以使其变为红色。

dragleave拖入子元素时是否可以防止触发?

2017 年更新: TL;DR,pointer-events: none;按照下面@HD 的回答中的描述查找 CSS ,它适用于现代浏览器和 IE11。

6个回答

你只需要保留一个引用计数器,当你得到一个 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 事件中,将 counter 重置为零,并清除添加的类。

你可以在这里运行

@Woody 我在这里的大量评论中没有看到他的评论,但是是的,这就是解决方法。为什么不将其纳入您的答案?
2021-03-18 11:12:09
天哪,这是最明显的解决方案,只有一票......来吧伙计们,你可以做得更好。我正在考虑这个问题,但在看到前几个答案的复杂程度后,我几乎放弃了它。你有什么缺点吗?
2021-03-26 11:12:09
当被拖动元素的边缘接触另一个可拖动元素的边缘时,这不起作用。例如一个可排序的列表;向下拖动元素,在下一个可拖动项目上不会将计数器递减回 0,而是停留在 1。我和pointer-events: none孩子们一起去了当我开始拖动时,我会附加一个具有此属性的类,当拖动结束时,我会删除该类。在 Safari 和 Chrome 上运行良好,但在 Firefox 上运行不佳。
2021-04-01 11:12:09
这适用于所有浏览器,但适用于 Firefox。我有一个新的解决方案,适用于我的情况。首先,dragenter我保存event.currentTarget在一个新变量中dragEnterTarget只要dragEnterTarget设置好了,我就忽略进一步的dragenter事件,因为它们来自儿童。在所有dragleave事件中,我检查dragEnterTarget === event.target. 如果这是错误的事件将被忽略,因为它是由孩子触发的。如果这是真的,我将重置dragEnterTargetundefined.
2021-04-03 11:12:09
很好的解决方案。我需要在处理接受放置的函数中将计数器重置为 0,否则后续的拖动无法按预期工作。
2021-04-09 11:12:09

拖入子元素时是否可以防止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;多次找到它,但是它可能与优雅的降级回退一起使用。至少它是如此简单并且解决了很多环境的问题。

指针事件属性是正确的解决方案,但不幸的是它在 IE8-IE10 中不起作用,IE8-IE10 仍然被广泛使用。另外,我应该指出您当前的 jsFiddle 甚至不能在 IE11 中工作,因为它没有添加必要的事件侦听器和默认行为预防。
2021-03-21 11:12:09
对于放置目标内的交互式子元素,例如按钮:添加pointer-events: none;到类。使用ondragenter将该类应用于放置目标然后从ondragleave的放置目标中删除该类
2021-03-22 11:12:09
它仅在区域内没有其他控制器元素(编辑、删除)时才有效,因为该解决方案也会阻止它们。
2021-03-26 11:12:09
使用指针事件确实是一个很好的答案,在我自己发现之前我挣扎了一下,那个答案应该更高。
2021-04-03 11:12:09
如果您的孩子是 Button 怎么办?oO
2021-04-08 11:12:09

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

我设法解决同样的问题,我在这最近已经感谢回答的答案,并认为这可能是有帮助的人谁谈到通过这个页面。整个想法是存储evenet.targetondrageenter任何父或子元素的每次它被调用。然后ondragleave检查当前目标(event.target)是否等于您存储的对象ondragenter

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

这工作正常的原因是当鼠标离开一个元素(比如el1)并进入另一个元素(比如el2)时,首先el2.ondragenter调用 ,然后调用el1.ondragleave只有当拖动离开/进入浏览器窗口时,event.target才会''el2.ondragenter 和 中el1.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)更大,以便用户可以轻松地将文件拖放到适当的位置。

这无助于解决重复 Dragenter 事件的问题
2021-03-21 11:12:09
这是最好的解决方案,它比计数器更好(特别是如果您委托事件)并且它也适用于可拖动的子级。
2021-03-23 11:12:09
这是否适用于作为 css 伪元素或伪类的“孩子”?我无法做到,但也许我做错了。
2021-03-27 11:12:09
到 2020 年 3 月,我也很幸运地使用了这个解决方案。注意:一些 linter 可能会抱怨使用==over ===您正在比较事件目标对象引用,因此也===可以正常工作。
2021-04-07 11:12:09

在这里,最简单的跨浏览器解决方案(认真的):

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 内创建了一个“遮罩”,宽度和高度继承,位置绝对,只会在拖动开始时显示。
因此,在显示该蒙版后,您可以通过在其上附加其他拖放和拖放事件来实现。

离开或放下后,您只需再次隐藏面具即可。
简单,没有并发症。

(观察:Greg Pettit 建议——您必须确保蒙版悬停在整个框上,包括边框)

不知道为什么,但它与 Chrome 不一致。有时离开该区域会使蒙版保持可见。
2021-03-29 11:12:09
请注意,您的 jsfiddle 有一个错误,在拖放中,您应该删除“#box”而不是“#box-a”上的悬停类
2021-03-30 11:12:09
当实际掩码是#drop::beforeor时,此解决方案效果很好::after另外,请注意有时,当快速拖动时,“dragleave”会在“dragenter”完成之前触发。如果 dragenter 添加类/伪元素,然后将 dragleave 删除,这可能会导致问题。
2021-04-01 11:12:09
这是一个不错的解决方案,但不知何故它对我不起作用。我终于想出了一个解决方法。对于那些正在寻找其他东西的人。你可以试试这个:github.com/bingjie2680/jquery-draghover
2021-04-04 11:12:09
其实这就是境界。让蒙版与边框重叠,没有自己的边框,它应该可以正常工作。
2021-04-13 11:12:09

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

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

更新:

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

当 dropzone 用于其他不触发 dragstart 事件的可拖动对象时,该单个 draggable 向 dropzone 添加了不会出现的效果。也许将所有内容都用作拖放效果的拖放区,同时将真正的拖放区与其他处理程序一起使用可以做到这一点。
2021-03-18 11:12:09
此答案显示了不需要触发的解决方法,但忽略了“在拖动到子元素时是否可以防止 dragleave 触发?”的问题。完全。
2021-03-20 11:12:09
@HD 我用有关使用指针事件 CSS 属性的信息更新了我的答案,以防止该dragleave事件在 Chrome、Firefox 和 IE11+ 中触发。除了 IE10 之外,我还更新了我的其他解决方法以支持 IE8 和 IE9。故意仅在拖动“拖动我”链接时添加 dropzone 效果。其他人可以根据需要随意更改此行为以支持他们的用例。
2021-03-26 11:12:09
从浏览器外部拖动文件时,行为可能会变得奇怪。在 Firefox 中,我有“进入孩子 -> 进入父母 -> 离开孩子 -> 进入孩子 -> 离开孩子”而没有离开父母,离开了“结束”类。旧的 IE 需要一个 attachEvent 替换 addEventListener。
2021-04-01 11:12:09
该解决方案在很大程度上取决于冒泡,应强调所有 addEventListener 中的“false”是必不可少的(尽管这是默认行为),因为许多人可能不知道这一点。
2021-04-12 11:12:09