悬停子元素时触发HTML5 dragleave


300

我遇到的问题是,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 ,该CSS 在现代浏览器和IE11中均有效。


截至2012年5月,据报告pimvdb的错误仍在Webkit中存在。我通过在拖动中添加一个类来对付它,由于它经常触发,因此并不能很好地解决该问题,但似乎可以解决该问题。
ajm 2012年

2
@ajm:谢谢,在一定程度上可行。但是,在Chrome上,进入或离开子元素时会闪烁,可能是因为 dragleave在这种情况下仍会触发。
pimvdb

我已经打开了jQuery UI bug 更新,我们欢迎他们投票,以便他们可以决定在上面投入资源
fguillen 2012年

@fguillen:对不起,但这与jQuery UI无关。实际上,甚至不需要jQuery即可触发该错误。我已经提交了WebKit错误,但是到目前为止还没有更新。
pimvdb 2012年

2
@pimvdb为什么不接受高清的答案?
TylerH

Answers:


337

您只需要保留一个参考计数器,在获得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');
        }
    }
});

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

你可以在这里运行


8
OMG,这是最明显的解决方案,只有一票...来吧,人们可以做得更好。我当时在想,但在看到前几个答案的复杂程度之后,我几乎舍弃了它。你有什么缺点吗?
Arthur Corenzan 2014年

11
当要拖动的元素的边缘接触另一个可拖动元素的边缘时,此方法不起作用。例如一个可排序的列表;向下拖动元素,在下一个可拖动项目上不会将计数器递减为0,而是停留在1。但是,如果我将其横向拖动到其他任何可拖动元素之外,它将起作用。我和pointer-events: none孩子们一起去了。当我开始拖动时,我会追加一个具有此属性的类,当拖动结束时,我将删除该类。在Safari和Chrome上效果很好,但在Firefox上效果不佳。
Arthur Corenzan

13
这适用于所有浏览器,但适用于Firefox。我有一个适用于我的情况的新解决方案。首先,dragenter我保存event.currentTarget一个新变量dragEnterTarget。只要dragEnterTarget设置好,我将忽略其他dragenter事件,因为它们来自孩子。无论如何dragleave我都会检查dragEnterTarget === event.target。如果为假,则事件将被忽略,因为它是由孩子触发的。如果是这样,我将重置dragEnterTargetundefined
Pipo

3
很好的解决方案。我需要在处理接受下降的函数中将计数器重置为0,否则后续的拖动将无法正常工作。
利亚姆·约翰斯顿

2
@Woody在这里的大量评论中我没有看到他的评论,但是是的,这就是解决方法。为什么不将其纳入您的答案?
英国电信

93

拖动到子元素中时,是否可以防止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中不起作用。另外,我应该指出,您当前的jsFiddle甚至无法在IE11中运行,因为它没有添加必要的事件侦听器和默认行为预防功能。
西奥多·布朗

2
使用指针事件确实是一个很好的答案,在我自己发现这个答案应该更高之前,我做了一些努力。
floribon 2014年

对于我们中的那些人来说,这是个不错的选择,它很幸运不必支持旧的浏览器。
卢克·汉斯福德

7
如果您的孩子是纽扣怎么办?oO
David

9
它仅适用,如果你没有在区域内其他控制器单元(编辑,删除),因为该解决方案块他们太..
佐尔坦SULE

45

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

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”,继承了width和height,位置为绝对位置,它将在拖动开始时显示。
因此,在显示该蒙版之后,您可以通过在其上附加其他dragleave&drop事件来实现此目的。

离开或放下后,只需再次遮罩即可。
简单而没有并发症。

(提示:Greg Pettit建议-您必须确保面具遮盖了整个盒子,包括边框)


不知道为什么,但是它不能与Chrome保持一致。有时离开该区域会使面罩可见。
格雷格·佩蒂特

2
实际上,这是边界。使遮罩与边框重叠,没有自己的边框,并且应该可以正常工作。
格雷格·佩蒂特

1
请注意,您的jsfiddle中存在一个错误,在drag_drop中,应删除“ #box”而不是“#box-a”上的悬停类

这是一个不错的解决方案,但是以某种方式对我不起作用。我终于找到解决方法。对于那些正在寻找其他东西的人。您可以尝试以下方法:github.com/bingjie2680/jquery-draghover
bingjie2680

不。我不想失去对子元素的控制。我已经制作了一个简单的jQuery插件,可以解决为我们完成任务的问题。检查我的答案
卡·法乔利

38

询问此问题已经有一段时间了,并且提供了许多解决方案(包括丑陋的hack)。

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

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

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


4
这是最好的解决方案,它比计数器要好(特别是如果您委派事件),它也适用于可拖动的子代。
2015年

3
这无助于解决重复的Dragenter事件的问题
BT

这对css伪元素或伪类的“孩子”有用吗?我无法理解,但也许我做错了。
Josh Bleecher Snyder

1
截至2020年3月,我也对这种解决方案感到非常幸运。注意:一些短毛绒可能会抱怨使用==over ===。您正在比较事件目标对象引用,因此也===可以正常工作。
亚历克斯

33

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

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

更新:

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


该答案显示了针对不良触发的一种解决方法,但忽略了以下问题:“是否有可能在拖动到子元素中时防止Dragleave触发?” 完全。
2013年

从浏览器外部拖动文件时,行为会变得很奇怪。在Firefox中,我没有离开父级就得到了“进入子级->进入父级->离开子级->进入子级->离开子级”,剩下的是“ over”类。旧版IE需要为addEventListener替换attachEvent。
2013年

该解决方案在很大程度上取决于冒泡,因此应强调所有addEventListener中的“ false”都是必不可少的(尽管这是默认行为),因为许多人可能对此一无所知。
2013年

当拖放区域用于其他不会触发dragstart事件的可拖动对象时,该单个可拖动对象会向该拖放区域添加一种效果,该效果不会出现。也许将所有内容用作拖放效果的拖放区,同时将真实的拖放区与其他处理程序保持在一起就可以做到这一点。
2013年

1
@HD我用有关使用指针事件CSS属性的信息更新了我的答案,以防止dragleave在Chrome,Firefox和IE11 +中触发该事件。除IE10外,我还更新了其他解决方法以支持IE8和IE9。故意仅在拖动“拖动我”链接时才添加dropzone效果。其他人可以根据需要随意更改此行为,以支持其用例。
西奥多·布朗

14

问题在于,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');
               }
           }

1
谢谢!但是,我无法在Chrome中使用它。您能提供一个有用的技巧吗?
pimvdb

3
我当时也在考虑通过检查坐标来做到这一点。您为我完成了大部分工作,谢谢。不过,我必须进行一些调整:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof

它不会在Chrome中运行,因为它的事件没有e.xand e.y
横街

我喜欢这个解决方案。首先无法在Firefox中使用。但是,如果将ex替换为e.clientX,将ey替换为e.clientY,则可以使用。在Chrome中也可以使用。
妮可·斯图兹

对于我来说,没有用Chrome的工作方式,ChrisDaniel Stuts的建议也没有用
Timo

14

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


2
极好的答案。
broc.seib

谢谢哥们,我喜欢普通的javascript方法。
Tim Gerhard

在具有的元素上使用时border-radius,将指针移至靠近拐角的位置实际上会离开该元素,但是此代码仍会认为我们在里面(我们离开了元素,但仍在边界矩形内)。这样dragleave事件处理程序将根本不会被调用。
杰·达德尼亚

11

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

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


11

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

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

这很棒!感谢您分享@kenneth,您拯救了我的一天!其他许多答案仅适用于只有一个可放置区域的情况。
安东尼

对我来说,它可以在Chrome和Firefox中使用,但id在Edge中不起作用。请参阅:jsfiddle.net/iwiss/t9pv24jo
iwis

8

很简单的解决方案:

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

这似乎在Safari 12.1中不起作用
Adam Taylor,

7

您可以通过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中不起作用。 这是实现上述代码的版本


非常感谢,但实际上它的Chrome我试图解决这个问题。
pimvdb

5
@pimvdb我看到您已经记录了一个错误,如果有人遇到此问题,我将在此保留对它的引用。
robertc 2011年

我确实做到了,但是我忘了在此处添加链接。谢谢你这样做。
pimvdb 2011年

Chrome错误已得到修复。
phk

7

这就是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')»吗?
Aldekein 2012年

1
效果很好,但浏览器上可能还有另一个窗口,因此获取鼠标位置并将其与矩形屏幕区域进行比较是不够的。
高清

我同意@HD另外,border-radius如我在上面对@azlar答案的评论中所解释的那样,当元素很大时,这也会引起问题。
杰·达德尼亚

7

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

希望这能奏效,这是一个小提琴


3
开箱即用对我来说不起作用。我需要使用它event.clientX, event.clientY,因为它们是相对于视口而不是页面的。我希望这可以帮助另一个迷失的灵魂。
乔恩·艾布拉姆斯

7

一个简单的解决方案是将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>


我遇到了同样的问题,您的解决方案效果很好。给其他人的提示,这是我的情况:如果您要忽略的子元素是拖动事件的克隆(试图实现视觉预览),则可以在创建克隆时在dragstart中使用它:dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche

4

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

1
暴躁但聪明。我喜欢。为我工作。
cupcakekid

1
哦,我希望这个答案有更多票!谢谢
Kirby 2015年

4

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


非常有用(至少对我只关心Chrome的我而言)。
M Katz

4

我遇到了同样的问题,并尝试使用pk7s解决方案。它可以工作,但是可以在没有任何额外dom元素的情况下做得更好。

基本上,想法是相同的-在可投放区域上方添加一个额外的不可见覆盖。只允许这样做,而无需任何额外的dom元素。这是CSS伪元素开始发挥作用的部分。

Java脚本

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/

我希望它可以帮助遇到相同问题的任何人。


1
有时在Chrome上不起作用(无法正确捕捉Dragleave)
Timo

4

只需检查所拖动元素是否为子元素(如果为子元素),就不要删除“ dragover”样式类。非常简单,对我有用:

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

看起来对我来说是最简单的解决方案,它解决了我的问题。谢谢!
弗朗切斯科·马尔凯蒂-斯塔西

3

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

为了简单起见,我将使用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)

就是这样。:)之所以有效,是因为即使onChild在子级上触发onEnter在父级onLeave之前触发,我们也会稍稍反转顺序以使其延迟,因此先删除类,然后在毫秒后重新添加类。


此代码段唯一要做的就是防止在下一个滴答周期重新应用“悬停”类(使“ dragleave”事件无效)。
无效

这不是没有用的。如果您离开父母,它将按预期工作。该解决方案的强大之处在于它的简单性,不理想或最佳。更好的解决方案是在子级上标记一个Enter,在onleave检查中是否刚刚输入了子级,如果不是,则触发请假事件。它会hovever需要测试,额外的警卫,checing孙辈等
马辛Raczkowski

3

我编写了一个名为滴落的拖放模块,该模块修复了这种怪异行为。如果您正在寻找一个好的低层拖放模块,可以将其用作所有内容的基础(文件上传,应用内拖放,从外部源到外部的拖放),则应选中此选项模块输出:

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

2

替代的工作解决方案,简单一些。

//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浏览器中,此功能似乎并不总是能正常工作。clientX当鼠标在盒子外面时,我有时会收到大于0的值。当然,我的元素是position:absolute
恒街

它是一直发生还是仅在某些时候发生?因为如果鼠标移动太快(例如在窗口外),则可能会得到错误的值。
Profet

它发生90%的时间。在极少数情况下(每10次中有1次)我可以使其达到0。我会尝试再次将鼠标移动得更慢,但是我不能说我移动得很快(也许您称之为正常速度)。
横街

2

花了很多小时后,我才完全按照预期工作了。我只想提供一个提示,仅当文件被拖过,文档拖过,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
});

1

我有一个类似的问题-当我将鼠标悬停在子元素上,使dropzone在Google Chrome中闪烁时,我在主体的dragleave事件上隐藏dropzone的代码被连续触发。

我可以通过安排隐藏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);

这也是我最终要做的,但是仍然很粗略。
mpen

1

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

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


1

解决了 ..!

声明任何数组,例如:

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

无需担心子元素。


1

即使阅读完所有这些答案后,我也为此付出了很多努力,并认为我可以与您分享我的解决方案,因为我认为这可能是较为简单的方法之一,尽管有些不同。我的想法是完全省略dragleave事件侦听器,并在每个新的Dragenter事件被触发时对dragleave行为进行编码,同时确保不会不必要地触发Dragenter事件。

在下面的示例中,我有一个表,希望通过拖放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事件处理功能,添加到每个表格单元格(除了dragstartdragoverdrop,和dragend处理程序,这是不具体到这个问题,所以这里没有复制):

/*##############################################################################
##                              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;
  }
}

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


0

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

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

0

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

2
如果用户改变主意并将文件拖回浏览器,它将保持红色。
ZachB

0

您可以将超时与transitioning标志一起使用,并在顶部元素上侦听。 dragenter/ dragleave来自子事件的气泡将上升到容器。

由于dragenter子元素dragleave在容器之前触发,因此我们将标志show设置为过渡时间为1ms ... dragleave侦听器将在1ms之前检查标志。

该标志仅在过渡到子元素期间为true,而在过渡到(容器的)父元素时为true

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/


0

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

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) {
                //
            }
        }
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.