Javascript 悬停子元素时触发 HTML5 dragleave
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7110353/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
HTML5 dragleave fired when hovering a child element
提问by pimvdb
The problem I'm having is that the dragleave
event of an element is fired when hovering a child element of that element. Also, dragenter
is not fired when hovering back the parent element again.
我遇到的问题dragleave
是当悬停该元素的子元素时会触发该元素的事件。此外,dragenter
再次悬停父元素时不会触发。
I made a simplified fiddle: http://jsfiddle.net/pimvdb/HU6Mk/1/.
我做了一个简化的小提琴:http: //jsfiddle.net/pimvdb/HU6Mk/1/。
HTML:
HTML:
<div id="drag" draggable="true">drag me</div>
<hr>
<div id="drop">
drop here
<p>child</p>
parent
</div>
with the following JavaScript:
使用以下 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");
}
});
What it is supposed to do is notifying the user by making the drop div
red when dragging something there. This works, but if you drag into the p
child, the dragleave
is fired and the div
isn't red anymore. Moving back to the drop div
also doesn't make it red again. It's necessary to move completely out of the drop div
and drag back into it again to make it red.
它应该做的是通过div
在拖动某些东西时将放置点设置为红色来通知用户。这是有效的,但如果你拖入p
孩子,dragleave
被解雇并且div
不再是红色的。回到水滴div
也不会让它再次变红。有必要完全移出水滴div
并再次拖回水滴以使其变为红色。
Is it possible to prevent dragleave
from firing when dragging into a child element?
dragleave
拖入子元素时是否可以防止触发?
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.
2017 年更新:TL;DR,pointer-events: none;
按照下面@HD 的回答中的描述查找 CSS ,它适用于现代浏览器和 IE11。
回答by Woody
You just need to keep a reference counter, increment it when you get a dragenter, decrement when you get a dragleave. When the counter is at 0 - remove the class.
你只需要保留一个引用计数器,当你得到一个 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');
}
}
});
Note: In the drop event, reset counter to zero, and clear the added class.
注意:在 drop 事件中,将 counter 重置为零,并清除添加的类。
You can run it here
你可以在这里运行
回答by H.D.
Is it possible to prevent dragleave from firing when dragging into a child element?
拖入子元素时是否可以防止dragleave触发?
Yes.
是的。
#drop * {pointer-events: none;}
That CSS seem to be enough for Chrome.
对于 Chrome 来说,CSS 似乎已经足够了。
While using it with Firefox, the #drop shouldn't have text nodes directly (else there's a strange issue where a element "leave it to itself"), so I suggest to leave it with only one element (e.g., use a div inside #drop to put everything inside)
在 Firefox 中使用它时,#drop 不应该直接有文本节点(否则有一个奇怪的问题,即元素“留给自己”),所以我建议只留下一个元素(例如,在里面使用 div #drop 把所有东西都放进去)
Here's a jsfiddlesolving the original question (broken) example.
I've also made a simplified versionforked from the @Theodore Brown example, but based only in this CSS.
我还从@Theodore Brown 示例中创建了一个简化版本,但仅基于此 CSS。
Not all browsers have this CSS implemented, though: http://caniuse.com/pointer-events
不过,并非所有浏览器都实现了此 CSS:http: //caniuse.com/pointer-events
Seeing the Facebook source code I could find this pointer-events: none;
several times, however it's probably used together with graceful degradation fallbacks. At least it's so simple and solves the problem for a lot of environments.
看到 Facebook 源代码我可以pointer-events: none;
多次找到它,但是它可能与优雅的降级回退一起使用。至少它是如此简单并且解决了很多环境的问题。
回答by Diego T. Yamaguchi
Here, the simplest Cross-Browser solution (seriously):
在这里,最简单的跨浏览器解决方案(认真的):
jsfiddle<-- try dragging some file inside the box
jsfiddle<-- 尝试在框中拖动一些文件
You can do something like that:
你可以这样做:
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);
In a few words, you create a "mask" inside the dropzone, with width & height inherited, position absolute, that will just show when the dragover starts.
So, after showing that mask, you can do the trick by attaching the others dragleave & drop events on it.
简而言之,您在 dropzone 内创建了一个“遮罩”,宽度和高度继承,位置绝对,只会在拖动开始时显示。
因此,在显示该蒙版后,您可以通过在其上附加其他拖放和拖放事件来实现。
After leaving or dropping, you just hide the mask again.
Simple and without complications.
离开或放下后,您只需再次隐藏面具即可。
简单而没有复杂性。
(Obs.: Greg Pettit advice -- You must be sure that the mask hover the entire box, including the border)
(观察:Greg Pettit 建议——您必须确保蒙版悬停在整个框上,包括边框)
回答by Ali Motevallian
It has been quite some time after this question is asked and a lot of solutions (including ugly hacks) are provided.
在提出这个问题并提供了许多解决方案(包括丑陋的黑客)之后已经有一段时间了。
I managed to fix the same problem I had recently thanks to the answer in this answerand thought it may be helpful to someone who comes through to this page.
The whole idea is to store the evenet.target
in ondrageenter
everytime it is called on any of the parent or child elements. Then in ondragleave
check if the current target (event.target
) is equal to the object you stored in ondragenter
.
我设法解决同样的问题,我在这最近已经感谢回答的答案,并认为这可能是有帮助的人谁谈到通过这个页面。整个想法是存储evenet.target
在ondrageenter
任何父或子元素的每次它被调用。然后ondragleave
检查当前目标(event.target
)是否等于您存储的对象ondragenter
。
The only case these two are matched is when your drag is leaving the browser window.
这两个匹配的唯一情况是当您的拖动离开浏览器窗口时。
The reason that this works fine is when the mouse leaves an element (say el1
) and enters another element (say el2
), first the el2.ondragenter
is called and then el1.ondragleave
. Only when the drag is leaving/entering the browser window, event.target
will be ''
in both el2.ondragenter
and el1.ondragleave
.
这工作正常的原因是当鼠标离开一个元素(比如el1
)并进入另一个元素(比如el2
)时,首先el2.ondragenter
调用 ,然后调用el1.ondragleave
。只有当拖动离开/进入浏览器窗口时,event.target
才会''
在el2.ondragenter
和 中el1.ondragleave
。
Here is my working sample. I have tested it on IE9+, Chrome, Firefox and Safari.
这是我的工作示例。我已经在 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;
};
})();
And here is a simple html page:
这是一个简单的 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>
With proper styling what I have done is to make the inner div (#file-drop-area) much bigger whenever a file is dragged into the screen so that the user can easily drop the files into the proper place.
通过适当的样式,我所做的是在将文件拖入屏幕时使内部 div(#file-drop-area)变得更大,以便用户可以轻松地将文件拖放到适当的位置。
回答by Theodore Brown
The "right" way to solve this issue is to disable pointer events on child elements of the drop target (as in @H.D.'s answer). Here's a jsFiddle I created which demonstrates this technique. Unfortunately, this doesn't work in versions of Internet Explorer prior to IE11, since they didn't support pointer events.
解决此问题的“正确”方法是禁用放置目标的子元素上的指针事件(如@HD 的回答)。这是我创建的一个 jsFiddle,它演示了这种技术。不幸的是,这在 IE11 之前的 Internet Explorer 版本中不起作用,因为它们不支持指针事件。
Luckily, I was able to come up with a workaround which doeswork in old versions of IE. Basically, it involves identifying and ignoring dragleave
events which occur when dragging over child elements. Because the dragenter
event is fired on child nodes before the dragleave
event on the parent, separate event listeners can be added to each child node which add or remove an "ignore-drag-leave" class from the drop target. Then the drop target's dragleave
event listener can simply ignore calls which occur when this class exists. Here's a jsFiddle demonstrating this workaround. It is tested and working in Chrome, Firefox, and IE8+.
幸运的是,我能够想出一个在旧版本的 IE中工作的解决方法。基本上,它涉及识别和忽略dragleave
拖动子元素时发生的事件。由于事件在父dragenter
节点上的dragleave
事件之前在子节点上触发,因此可以向每个子节点添加单独的事件侦听器,从放置目标中添加或删除“ignore-drag-leave”类。然后放置目标的dragleave
事件侦听器可以简单地忽略在此类存在时发生的调用。这是演示此解决方法的jsFiddle。它在 Chrome、Firefox 和 IE8+ 中经过测试和工作。
Update:
更新:
I created a jsFiddle demonstrating a combined solutionusing feature detection, where pointer events are used if supported (currently Chrome, Firefox, and IE11), and the browser falls back to adding events to child nodes if pointer event support isn't available (IE8-10).
我创建了一个 jsFiddle 演示了一个使用特征检测的组合解决方案,如果支持(目前是 Chrome、Firefox 和 IE11),则使用指针事件,如果指针事件支持不可用,浏览器会回退到向子节点添加事件(IE8 -10)。
回答by Greg
The problem is that the dragleave
event is being fired when the mouse goes in front of the child element.
问题是dragleave
当鼠标移到子元素前面时会触发事件。
I've tried various methods of checking to see if the e.target
element is the same as the this
element, but couldn't get any improvement.
我尝试了各种方法来检查e.target
元素是否与元素相同this
,但没有任何改进。
The way I fixed this problem was a bit of a hack, but works 100%.
我解决这个问题的方式有点小技巧,但 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');
}
}
回答by azlar
if you are using HTML5, you can get the parent's clientRect:
如果您使用的是 HTML5,则可以获得父级的 clientRect:
let rect = document.getElementById("drag").getBoundingClientRect();
Then in the parent.dragleave():
然后在 parent.dragleave() 中:
dragleave(e) {
if(e.clientY < rect.top || e.clientY >= rect.bottom || e.clientX < rect.left || e.clientX >= rect.right) {
//real leave
}
}
回答by FruityFred
A very simple solution is to use the pointer-events
CSS property. Just set its value to none
upon dragstarton every child element. These elements won't trigger mouse-related events anymore, so they won't catch the mouse over them and thus won't trigger the dragleaveon the parent.
一个非常简单的解决方案是使用pointer-events
CSS 属性。只需将其值设置为none
在每个子元素上的dragstart时。这些元素不会再触发与鼠标相关的事件,因此它们不会在它们上方捕捉鼠标,因此不会触发父级上的dragleave。
Don't forget to set this property back to auto
when finishing the drag ;)
auto
完成拖动时不要忘记将此属性设置回;)
回答by Kenneth Spencer
This fairly simple solution is working for me so far, assuming your event is attached to each drag element individually.
到目前为止,这个相当简单的解决方案对我有用,假设您的事件单独附加到每个拖动元素。
if (evt.currentTarget.contains(evt.relatedTarget)) {
return;
}
回答by Owen M
Very simple solution:
非常简单的解决方案:
parent.addEventListener('dragleave', function(evt) {
if (!parent.contains(evt.relatedTarget)) {
// Here it is only dragleave on the parent
}
}