hover子元素时触发HTML5 dragleave

我遇到的问题是,当hover该元素的子元素时,会触发元素的dragleave事件。 另外,当再次hover父元素时, 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"); } }); 

它应该做的是通过拖动红色的东西通过拖动的东西通知用户。 这是有效的,但是如果你拖到小孩dragleavedragleave被解雇, div也不会再变红了。 移回放置div也不会使其再次变红。 有必要完全移出放置格,然后再拖回去使其变成红色。

拖入子元素可以防止dragleave被触发吗?

2017更新: TL; DR,查找CSS pointer-events: none; 正如@ HD下面的答案中所描述的那样,在现代浏览器和IE11中都可以使用。

你只需要保留一个参考计数器,当你得到一个dragenter时递增,当你得到一个dragleave时递减。 当计数器在0时 – 删除class级。

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

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

你可以在这里运行它

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

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里面创build一个“mask”,并且inheritance宽度和高度,绝对位置,当dragover启动的时候会显示出来。
所以,在显示那个面具之后,你可以通过附加其他的dragleave和放置事件来实现这个function。

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

(Obs .:格雷格佩蒂特build议 – 你必须确保面具盘旋整个框,包括边框)

拖入子元素可以防止dragleave被触发吗?

是。

 #drop * {pointer-events: none;} 

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

当在Firefox中使用它时,#drop不应该直接有文本节点(否则有一个奇怪的问题,一个元素“离开自己” ),所以我build议只留下一个元素(例如,在里面使用一个div #把一切都放在里面)

这是一个jsfiddle解决原来的问题(破碎)的例子 。

我也做了@Theodore布朗例子分支的简化版本 ,但只基于这个CSS。

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

看到Facebook的源代码,我可以find这个pointer-events: none; 几次,但它可能与优雅的退化回退一起使用。 至less它是如此简单,并解决了很多环境的问题。

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

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

更新:

我创build了一个jsFiddle,演示了使用特性检测的组合解决scheme ,如果支持(当前为Chrome,Firefox和IE11),则使用指针事件,如果指针事件支持不可用,则浏览器会回退到向子节点添加事件(IE8 -10)。

这个问题已经有相当一段时间了,并提供了很多解决scheme(包括丑陋的黑客)。

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

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

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

这是我的工作示例。 我已经在IE9 +,Chrome,Firefox和Safari上testing过了。

 (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(#文件拖放区)大得多,使用户可以很容易地将文件放到适当的地方。

问题是当鼠标移动到子元素前面时, 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(ex > rect.left + rect.width || ex < rect.left || ey > rect.top + rect.height || ey < rect.top) { $(this).removeClass('red'); } } 

你可以从Firefox的jQuery源代码中获得一些灵感来修复它:

 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,因为dragleave似乎不存在dragleave事件,我假设你在Chrome中工作,因为你的例子在Firefox中没有工作。 这里是上述代码实现的版本 。

在这里,它是一个Chrome的解决scheme:

 .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 (ex > rect.left + rect.width - 1 || ex < rect.left || ey > rect.top + rect.height - 1 || ey < rect.top) { console.log('Drag is really out of area!'); } }) 

不知道这个跨浏览器,但我在Chrome中testing,它解决了我的问题:

我想在整个页面上拖放一个文件,但是当我拖动子元素时,我的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; }); 

我写了一个名为Dragster的小库来处理这个确切的问题,除了在IE中默默无闻(不支持DOM事件构造函数,但使用jQuery的自定义事件编写相似的东西相当容易)

我有同样的问题,并试图使用PK解决scheme。 它的工作,但可以做得更好一点,没有任何额外的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

此规则将创build一个完全覆盖的可投放区域的覆盖。

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

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

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

这是另一个使用document.elementFromPoint的解决scheme:

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

希望这个作品,这是一个小提琴 。

一个非常简单的解决scheme是使用pointer-events CSS属性 。 只需将每个子元素的dragstart值设置为none 。 这些元素不会再触发与鼠标有关的事件,所以他们不会将鼠标拖到它们上面,因此不会触发父级的拖动

当完成拖动时,不要忘记将此属性重新设置为auto ;)

我偶然发现了同样的问题,这里是我的解决scheme – 我认为这更容易。 我不确定是否是交叉浏览器(可能取决于冒泡命令)

我将使用jQuery来简化,但解决scheme应该是独立于框架的。

事件冒泡给任何方式如此给予:

 <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 on child在onLeave on parent之前触发,我们稍微延迟它的顺序,所以class先被移除,然后在一毫秒后重新获得。

一个替代的工作解决scheme,更简单一点。

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

花了那么多小时后,我得到了这个build议,完全按照预期工作。 我只想在拖动文件的时候提供一个提示,而文档dragover,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 }); 

我写了一个名为drip-drop的拖放模块来修复这个奇怪的行为,等等。 如果你正在寻找一个好的低级拖放模块,你可以用它作为任何事情的基础(file upload,应用程序内拖放,拖拽或外部源),你应该检查这个模块出来:

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

我有一个类似的问题 – 我的代码隐藏droplar在dragleave事件身体被抛出时hover子元素使谷歌浏览器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); 

只要检查拖动的元素是否是一个孩子,如果是,那么不要删除你的“dragover”风格的类。 很简单,为我工作:

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

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

从子元素派发的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 } }) 

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

您可以使用带有transitioning标志的超时,并监听顶部元素。 从小孩活动的dragenter / dragleave将泡到容器。

由于子元素上的dragenter在容器的dragleave之前dragleave ,所以我们将标志显示设置为过渡1ms。在1ms dragleave之前, dragleave侦听器将检查该标志。

该标志只有在转换到子元素时才是真实的,并且在转换到(容器的)父元素时不会是真实的,

 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/

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

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

使用此代码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。 它只会在目标被input时设置为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'); } }) 

圭多

如果您使用HTML5,则可以获取父级的clientRect:

 let parentRect = 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