【问题标题】:HTML5 dragleave fired when hovering a child element悬停子元素时触发 HTML5 dragleave
【发布时间】:2011-10-29 22:26:48
【问题描述】:

我遇到的问题是当悬停该元素的子元素时会触发该元素的 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,查找 CSS pointer-events: none;,如下面 @H.D. 的回答中所述,适用于现代浏览器和 IE11。

【问题讨论】:

  • 截至 2012 年 5 月,pimvdb 报告的错误仍然存​​在于 Webkit 中。我还通过在 dragover 中添加一个类来反驳它,因为它经常触发,所以它不是很好,但出现稍微修补一下问题。
  • @ajm:谢谢,这在一定程度上有效。但是,在 Chrome 上,进入或离开子元素时会出现闪烁,大概是因为在这种情况下仍然会触发 dragleave
  • 我开了一个jQuery UI bug 欢迎upvotes,这样他们就可以决定把资源放在上面
  • @fguillen:很抱歉,这与 jQuery UI 无关。事实上,甚至不需要 jQuery 来触发 bug。我已经提交了一个 WebKit 错误,但目前还没有更新。
  • @pimvdb,是的,我已经在我的错误中看到了答案,这是指向您的 WebKit 错误的链接?...我如何使用 FireFox 重现相同的错误:/

标签: javascript jquery html drag-and-drop jquery-events


【解决方案1】:

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

你可以运行它here

【讨论】:

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

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

是的。

#drop * {pointer-events: none;}

对于 Chrome 来说,CSS 似乎已经足够了。

在 Firefox 中使用时,#drop 不应该直接有文本节点(否则会有一个奇怪的issue where a element "leave it to itself"),所以我建议只保留一个元素(例如,在#drop 中使用 div 来放置里面的一切)

Here's a jsfiddle解决original question (broken) example

我还从@Theodore Brown 示例中派生了一个simplified version,但仅基于此CSS。

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

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

【讨论】:

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

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

感谢answer 中的答案,我设法解决了我最近遇到的同样问题,并认为这可能对访问此页面的人有所帮助。 整个想法是将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) 变得更大,以便用户可以轻松地将文件放到适当的位置。

【讨论】:

  • 这是最好的解决方案,它比计数器好(特别是如果你委托事件),它也适用于可拖动的孩子。
  • 这对于重复的dragenter事件的问题没有帮助
  • 这是否适用于作为 css 伪元素或伪类的“孩子”?我无法做到,但也许我做错了。
  • 截至 2020 年 3 月,我对这个解决方案也很幸运。注意:一些 linter 可能会抱怨使用 == 而不是 ===。您正在比较事件目标对象引用,因此 === 也可以正常工作。
【解决方案4】:

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

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);

简而言之,您可以在拖放区域内创建一个“蒙版”,继承宽度和高度,绝对位置,只会在拖动开始时显示。
因此,在显示该掩码后,您可以通过在其上附加其他拖放事件来完成此操作。

离开或放下后,你只需再次隐藏面具。
简单,没有复杂性。

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

【讨论】:

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

解决此问题的“正确”方法是禁用放置目标的子元素上的指针事件(如@H.D. 的回答)。 Here's a jsFiddle I created which demonstrates this technique。不幸的是,这在 IE11 之前的 Internet Explorer 版本中不起作用,因为它们是didn't support pointer events

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

更新:

我使用功能检测创建了a jsFiddle demonstrating a combined solution,如果支持(当前为 Chrome、Firefox 和 IE11),则使用指针事件,如果不支持指针事件(IE8),浏览器会退回到向子节点添加事件-10)。

【讨论】:

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

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

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

【讨论】:

  • 这太棒了!感谢分享@kenneth,你拯救了我的一天!仅当您有一个可放置的区域时,许多其他答案才适用。
  • 对我来说,它适用于 Chrome 和 Firefox,但 id 不适用于 Edge。请看:jsfiddle.net/iwiss/t9pv24jo
【解决方案7】:

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

here is a jsfiddle

【讨论】:

  • 优秀的答案。
  • 谢谢哥们,我喜欢普通的 javascript 方法。
  • 在具有border-radius 的元素上使用时,将指针移近角落会实际上离开元素,但这段代码仍会认为我们在里面(我们已经离开元素,但我们仍在边界矩形中)。那么dragleave 事件处理程序根本不会被调用。
  • 这对我来说非常有用,谢谢!
【解决方案8】:

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

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

【讨论】:

    【解决方案9】:

    一个简单的解决方法是在子组件中添加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>

    【讨论】:

    • 我遇到了同样的问题,您的解决方案效果很好。给其他人的提示,这是我的情况:如果您尝试忽略 drag 事件的子元素是被拖动元素的克隆(尝试实现视觉预览),您可以使用创建克隆时在 dragstart 内部:dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
    • 不错,简单的解决方案。人们可能想考虑只创建一个类.no-pointer-events {pointer-events: none;},然后将no-pointer-events 添加到每个子元素。完成。
    • 这应该是公认的答案,它是最简单的,并且不涉及任何黑客攻击。 :D
    • 但是如果子组件是一个链接呢?
    【解决方案10】:

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

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

    我解决这个问题的方法有点老套,但 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 中工作。你能提供一个你的hack的工作小提琴吗?
    • 我也想通过检查坐标来做到这一点。你为我做了大部分工作,谢谢:)。我不得不做一些调整:if (e.x &gt;= (rect.left + rect.width) || e.x &lt;= rect.left || e.y &gt;= (rect.top + rect.height) || e.y &lt;= rect.top)
    • 它在 Chrome 中不起作用,因为它的事件没有 e.xe.y
    • 我喜欢这个解决方案。首先在 Firefox 中不起作用。但是,如果您将 e.x 替换为 e.clientX 并将 e.y 替换为 e.clientY ,则它可以工作。也适用于 Chrome。
    • 我在 chrome 中无法使用,ChrisDaniel Stuts 建议的内容也没有
    【解决方案11】:

    非常简单的解决方案:

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

    【讨论】:

    【解决方案12】:

    您可以从jQuery source code 获得一点灵感,在 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 中不起作用。 Here's a version 已实现上述代码。

    【讨论】:

    • 非常感谢,但实际上我正在尝试在 Chrome 中解决这个问题。
    • @pimvdb 我看到你已经登录了a bug,我会在这里留下一个参考,以防其他人遇到这个答案。
    • 我确实做到了,但我忘记在此处添加指向它的链接。谢谢你这样做。
    • Chrome 的 bug 已同时修复。
    【解决方案13】:

    这里是 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!');
                        }
                    })
    

    【讨论】:

    • 是否进入«if (typeof event.clientX === 'undefined')»?
    • 运行良好,但浏览器上可能还有另一个窗口,因此获取鼠标位置并将其与矩形屏幕区域进行比较是不够的。
    • 我同意@H.D.此外,当元素具有较大的 border-radius 时,这会导致问题,正如我在上面对 @azlar 答案的评论中所解释的那样。
    【解决方案14】:

    这是另一个使用 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');
       }
    }
    

    希望这可行,这是fiddle

    【讨论】:

    • 开箱即用这对我不起作用。我需要改用event.clientX, event.clientY,因为它们是相对于视口而不是页面的。我希望这可以帮助另一个迷失的灵魂。
    【解决方案15】:

    我遇到了同样的问题并尝试使用 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

    此规则将为可放置区域创建一个完全覆盖的覆盖。

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

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

    希望对遇到同样问题的人有所帮助。

    【讨论】:

    • 有时在 chrome 上不起作用(不能正确捕捉 dragleave)
    • #box 必须是相对位置才能正常工作;但我能够让这个解决方案非常干净地工作。谢谢!
    【解决方案16】:

    不确定这是否是跨浏览器,但我在 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;
    });
    

    【讨论】:

    • Hacky 但很聪明。我喜欢。为我工作。
    • 哦,我多么希望这个答案有更多的投票!谢谢
    【解决方案17】:

    我偶然发现了同样的问题,这是我的解决方案 - 我认为这比上面的要容易得多。我不确定它是否是跨浏览器(可能取决于冒泡顺序)

    为了简单起见,我将使用 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 在父级上触发,我们也会稍微延迟它以颠倒顺序,因此首先删除类,然后在一毫秒后重新响应。

    【讨论】:

    • 这个 sn-p 所做的唯一一件事就是通过在下一个滴答周期重新应用“悬停”类来防止它被删除(使“dragleave”事件无用)。
    • 这不是没用的。如果您离开父母,它将按预期工作。这个解决方案的强大之处在于它很简单,它并不理想或最好。更好的解决方案是在孩子上标记输入,在 onleave 检查我们是否刚刚输入孩子,如果是则不触发离开事件。它需要测试、额外的警卫、检查孙子等。
    【解决方案18】:

    另一种可行的解决方案,稍微简单一些。

    //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) {
    

    【讨论】:

    • 这在 Chrome 中似乎并不总是有效。当鼠标在框外时,我有时会收到高于 0 的clientX。当然,我的元素是position:absolute
    • 这种情况是一直发生还是偶尔发生?因为如果鼠标移动得太快(例如在窗口外),您可能会得到错误的值。
    • 90% 的时间都会发生这种情况。在极少数情况下(10 次中的 1 次)我可以使其达到 0。我会再次尝试缓慢移动鼠标,但我不能说我移动得很快(也许你会称之为正常速度)。
    • 使用 dropzone.js 类似的方法对我有用:console.log(e.clientX + "/" + e.clientY ); if (e.clientX == 0 &amp;&amp; e.clientY == 0 ) { console.log('REAL leave'); }
    【解决方案19】:

    我已经编写了一个名为 Dragster 的小库来处理这个确切的问题,除了在 IE 中默默地不做任何事情之外,它在任何地方都可以工作(它不支持 DOM 事件构造函数,但使用jQuery 的自定义事件)

    【讨论】:

    • 非常有用(至少对于我只关心 Chrome 的我来说)。
    【解决方案20】:

    只需检查拖动的元素是否是子元素,如果是,则不要删除您的 'dragover' 样式类。很简单,对我有用:

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

    【讨论】:

    • 对我来说似乎是最简单的解决方案,它解决了我的问题。谢谢!
    【解决方案21】:

    我编写了一个名为drip-drop 的拖放模块来修复这种奇怪的行为等。如果您正在寻找一个好的低级拖放模块,您可以将其用作任何事情的基础(文件上传、应用内拖放、从外部源拖放或从外部源拖放),您应该检查这个模块输出:

    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
        }
    });
    

    【讨论】:

      【解决方案22】:

      我找到了解决这个问题的简单方法,所以分享它。在我的情况下效果很好。

      jsfiddle试试看。

      您实际上只能通过dragenter 事件来实现这一点,甚至不需要注册dragleave。您所需要的只是在您的放置区周围有一个禁止放置区域,仅此而已。

      您还可以有嵌套的拖放区,这非常有效。也检查一下nested 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");
        }
      });
      

      【讨论】:

      • 我试过这个解决方案,但应该注意它有一个限制:当按 Esc 取消拖放操作时,没有人会从你的 dropzone 元素中删除“over”类。如果您尝试为此使用“dragleave”事件,您只需转到原作者询问的第一个问题。因此,悬停子元素并在拖动时按 Esc 键都会在 dropzone 上引发“dragleave”事件。也许我们还需要监听 Esc 键以从 dropzone 中删除“over”类...
      • 嗨@mar​​10,感谢您指出这个问题,我会更新我的答案。我认为我们可以使用dragend 事件来处理这个问题,但我需要对其进行测试。
      • 我还没有找到一种方法来检测dragend事件是否是通过释放鼠标按钮或按下Esc键触发的,所以使用dragend事件会使逻辑复杂化,所以简单的解决方案是按照您所说的检测 Escape 键。我已经更新了答案。
      【解决方案23】:

      在花了这么多时间之后,我得到了这个建议完全按照预期工作。我只想在文件被拖过时提供提示,并且文档拖过,dragleave 会导致 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
      });
      

      【讨论】:

        【解决方案24】:

        "dragleave" 事件在鼠标离开目标容器的拖动区域时触发。

        这很有意义,因为在许多情况下,只有父级可以droppable,而不是后代。 我认为 event.stopPropogation() 应该处理这种情况,但似乎没有解决问题。

        上面提到的一些解决方案似乎确实适用于大多数情况,但在那些不支持 dragenter / dragleave 事件的孩子的情况下失败,例如 iframe。

        1 解决方法是检查 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");
        });
        

        你可以找到一个工作小提琴here

        【讨论】:

          【解决方案25】:

          我遇到了类似的问题 — 当悬停子元素时,我的用于在 dragleave 事件上隐藏 dropzone 的代码在 Google Chrome 中被触发,从而使 dropzone 闪烁。

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

          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);
          

          【讨论】:

          • 这也是我最终做的,但它仍然粗略。
          【解决方案26】:

          解决了..!

          为 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');
          }
          

          无需担心子元素。

          【讨论】:

            【解决方案27】:

            即使在阅读了所有这些答案之后,我也为此苦苦挣扎,并认为我可以与您分享我的解决方案,因为我认为它可能是更简单的方法之一,尽管有些不同。我的想法是完全省略 dragleave 事件侦听器,并在触发每个新的 dragenter 事件时对 dragleave 行为进行编码,同时确保不会触发 dragenter 事件。

            在下面的示例中,我有一个表格,我希望能够通过拖放 API 相互交换表格行内容。在dragenter 上,应将CSS 类添加到您当前拖动元素的行元素中,以突出显示它,在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;
              }
            }
            

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

            【讨论】:

              【解决方案28】:

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

              .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');
              }
              

              【讨论】:

                【解决方案29】:

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

                从子元素调度的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
                    }
                })
                

                【讨论】:

                  【解决方案30】:

                  pimvdb..

                  您为什么不尝试使用 drop 而不是 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");
                                   }
                                  });
                  

                  【讨论】:

                  • 如果用户改变主意并将文件拖出浏览器,它将保持红色。
                  猜你喜欢
                  • 2015-04-16
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-08-03
                  • 2013-05-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多