I'm aware of the different event models in Javascript (the WC3 model versus the Microsoft model), as well as the difference between bubbling and capturing. However, after a few hours reading various articles about this issue, I'm still unsure how to properly code the following seemingly simple behavior:
If I have an outer div and an inner div element, I want a single mouse-out event to be triggered when the mouse leaves the outer-div. When the mouse crosses from the inner-div to the outer-div, nothing should happen, and when the mouse crosses from the outer-div to the inner-div nothing should happen. The event should only fire if the mouse moves from the outer-div to the surrounding page.
<div id="outer" style = "width:20em; height:20em; border:1px solid #F00" align = "center" onmouseout="alert('mouseout event!')" >
<div id="inner" style = "width:18em; height:18em; border:1px solid #000"></div>
</div>
Now, if I place the "mouseout" event on the outer-div, two mouse-out events are fired when the mouse moves from the inner-div to the surrounding page, because the event fires once when the mouse moves from inner to outer, and then again when it moves from outer to the surrounding page.
I know I can cancel the event using ev.stopPropagation(), so I tried registering an event handler with the inner-div to cancel the event propagation. However, this won't prevent the event from firing when the mouse moves from the outer-div to the inner-div.
So, unless I'm overlooking something, it seems to me this behavior can't be accomplished without complex mouse-tracking functions. In the future, I plan to reimplement a lot of this code using a more advanced framework, like JQuery, but for now, I'm wondering if there is a simple way to implement the above behavior in regular Javascript.
The mouseout event on the inner div ‘bubbles’ to the outer div. To detect that this has happened from the outer div, check the target property of the event:
<div id="outer">
<div id="inner">x</div>
</div>
document.getElementById('outer').onmouseout= function(event) {
// deal with IE nonsense
if (event===undefined) event= window.event;
var target= 'target' in event? event.target : event.srcElement;
if (target!==this) return;
...
};
The usual problem with mouseout is you get it when the pointer moves “out” of the parent even if it's only moving “in” to the child. You can detect this case manually by looking up the ancestor list of the element the mouse is moving into:
var other= 'relatedTarget' in event? event.relatedTarget : event.toElement;
while ((other= other.parentNode).nodeType===1)
if (other===this) return;
This is the mousein/mouseout model: it is only interested about which element is the mouse's immediate parent. What you more often want is the mouseenter/mouseleave model, which considers element trees as a whole, so you'd only get mouseleave when the pointer was leaving the element-or-its-descendants and not moving directly into the element-or-its-descendants.
Unfortunately mouseenter/mouseleave is currently an IE-only event pair. Hopefully other browsers will pick it up as it is expected to be part of DOM Level 3 Events.
Related
Still trying to combine drag&drop and moving an element with the mouse I'm struggling with dragenter and dragleave not being called for a parent element when I'm moving the child element. This seems quite natural because the element always hovers over the parent element and prevents dragenter being called for the parent.
I tried to call stopPropagation() and preventDefault() in dragenter, dragleave, dragover, dragstart and drag events for the child element but with no real effect.
Another question seems to address a similar issue but with no real solution if I get this correctly.
Maybe it's just too dark down here in the rabbit hole to see the obvious - how do I prevent the dragged item from avoiding its parents dragenter/dragleave events to be called?
On another level I just want to know if the element has been dropped outside the parent element (to then return it to it's original position). Is there an easier approach?
Here is my current code - In the current state the element is being moved with the mouse and thus preventing dragenter or dragleave being called.
Deactivating the actual movement of draggable_element will make dragenter and dragleave be called when leaving the parent/target area but also when the dragged element is being entered (what I somehow can't avoid).
Found it - instead fiddling with the drag/drop events you just have to deactivate pointer-events for the dragged item to avoid unwanted dragenter/dragleave events for the parent and turn it back on again afterwards (it has to be activated by default to enable dragging in the first place).
draggable_element.addEventListener("dragstart", (e) => {
e.srcElement.style.pointerEvents = "none";
... // rest of code
});
elem.addEventListener("dragend", (e) => {
e.srcElement.style.pointerEvents = "auto";
... // rest of code
});
Here is a working example: https://jsfiddle.net/03a9s4ur/10/
Not sure if it's my profound misunderstanding of React events or an actual bug, but mouse events registered on the parents sometimes fire on the children.
In the bin below, mousing around the two divs will eventually result in the inner div getting highlighted red, even though it doesn't have an event trigger for attaching the ui-hover class (though its parent does).
http://jsbin.com/vemopo/1/edit?css,js,output
It seems to depend on how fast the mouse is moving. My guess is, event.target becomes whatever is under the mouse when the event is triggered. So it fires when entering the parent div, but if the mouse is moving quickly then it may already be hovering the child div when the event handler is processed.
(Updated answer)
As #Stan commented, replacing event.target with event.currentTarget is the simplest fix, it will target the element whose listener triggered the event rather than the element under the mouse.
(Original answer / other options)
You can also set ref="target" on the parent div and then use the ref rather than the event target.
_mouseEnter: function(event) {
this.refs.target.getDOMNode().classList.add('ui-hover');
},
However, it may be preferable to avoid touching the DOM like this. In that case you could use setState in the event handlers and use conditionals to give a different result depending on this.state.
_mouseEnter: function(event) {
this.setState({
hovering: true
});
}
I would like to design something similar to what can be seen on http://www.thedana.com/ by the "Check availability" button - I've used the jquery.js file from w3school.com and got the following so far: http://quaaoutlodge.com/drupal-7.14/ (Book Now tab). Now as you realize, it is very touchy and fades out sometimes when it shouldn't (when the cursor is still in the middle of the field) how can I make this nicer, more user friendly?
Thanks!
Ron
Update:
I tried to implement that but it doesn't quite work as I would like to show my "fade" div upon hovering over "book" and keep it up as the cursor moves down, over "fade" how do I accomplish this?
Url:http://quaaoutlodge.com/drupal-7.14/
Put the div#fade inside of the div#book, that will solve half of your problems. You will have to adapt the CSS as well for this change.
Another very important point to learners is that jQuery provides unobtrusive cross-browser event listeners attaching. That means, inline JS in the html as onmouseenter="handler()" is not just unnecessary and technically ugly - mixed structure with behavior -, it also pollutes the global scope with function names.
That's one of the reasons people advertise against W3School.
But back on topic here's a solution using the DOM Ready handler and a hover one:
Fiddle
HTML
<div id="book">
Book Now
<div id="fade">TEST</div>
</div>
JS
$(function() {
var fade = $('#fade');
$('#book').hover(function() {
fade.fadeIn();
}, function() {
fade.fadeOut();
});
});
Again, you will have to rework the CSS removing the position:absolute and margins from #fade.
Can you try with jquery's .mouseleave instead of the generic onmouseout?
http://api.jquery.com/mouseleave/
"The mouseleave event differs from mouseout in the way it handles event bubbling. If mouseout were used in this example, then when the mouse pointer moved out of the Inner element, the handler would be triggered. This is usually undesirable behavior. The mouseleave event, on the other hand, only triggers its handler when the mouse leaves the element it is bound to, not a descendant. So in this example, the handler is triggered when the mouse leaves the Outer element, but not the Inner element."
When entering a DOM element, mouseover event will happen. Upon moving the mouse around the current element, no event happens, as mouseover is for entering.
However, this rule is not obeyed for child nodes. If moving the mouse over child nodes, the mouseover event will be triggered again and again, though no new event, as we are still in the original parent.
See this example. If we move the mouse over the parent (actually on its textNode), nothing new happens, but if we go to the child element (still on the parent), it will trigger the mouseover event again and again. In fact it will fire the mouse event every time mouse enters an elements (even inside the original parent element).
How we can make the mouseover only once for moving all over the parent (the original element in the addEventListener)? In the given example, I mean to avoid firing the event upon moving the mouse on the child element.
What you really need is the mouseenter event, which does not bubble (unlike mouseover). From MDN:
The synchronous mouseenter DOM event is dispatched when a mouse or
another pointing device enters the physical space given to the element
or one of its descendants.
Similar to mouseover , it differs in that it doesn't bubble and that
it isn't sent when the pointer is moved from one of its descendants'
physical space to its own physical space.
Unfortunately, it's not widely supported. One solution is to use jQuery (see .mouseenter()), which polyfills that event in all the browsers it supports. Another option is to check the element that triggered the event:
document.getElementById("parent").addEventListener('mouseover', function(e) {
if (e.target.id === "parent") { //Only execute following on parent mouseover
document.getElementById("time").innerHTML = new Date;
this.childNodes[1].style.display="block";
}
}, false);
Or see here for what looks at first glance to be a pretty good shim.
This works for me in chrome and ff
document.getElementById("parent").addEventListener('mouseover', function(event) {
var e = event.fromElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
return;
}
document.getElementById("time").innerHTML = new Date();
}, true);
Fiddle Demo
Reference: Mouse Events
in your example child element is not firing any event it is your parent element on which when you mouseover it runs your script and display result in your "time" div.
I am trying to drag an image with Javascript (no libraries). I am able to listen to mousedown and mousemove events. For some reason, I am not able to capture the mouseup event after mousemove. (I can capture mouseup if it is a click but not if it is a drag).
I have tried to listen to the event on document, window, and the image.
Here's the url to my test page:
https://dl-web.dropbox.com/get/Public/move.html?w=74a0d498
Any help on this would be greatly appreciated!
Found the issue, if it is going to be of help to anyone:
I added event.preventDefault(); in the mousedown event and now I am getting mouseup notifications.
Strangely, I've found that when I set my text as unselectable using the below CSS, that inhibits the mouseup event from firing as well -- perhaps this will help someone else.
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
I have seen the mouseup event not fire on my target div because its parent div began consuming drag events. I clicked my target div and this caused my mousedown and mousemove handlers to run. I was expecting to see a mouseup but I did not.
In my case, I had a parent div living beneath my target div in the same location where I launched my mouse button click, and instead of bubbling up the mouseup event on my document once I let go of the left mouse button, I instead got a dragend event fire on its parent div.
The solution for me was simply to set user-select: none; CSS property on the parent div which houses my target div and to make sure I set user-select: text on my target div. This property seems to disable dragging for my parent div and because it has stopped consuming drag events, my mouseup event now properly bubbles its way up.
I presume the reason why this might happen is because the browser starts thinking your mouse move is actually part of a drag event on another element, at which point it seems to stop reporting the standard mouse events and switches to drag events instead.
I was running into this exact same issue! Adding event.preventDefault(); worked for me but I was forced to add it to both the mousedown and mousemove functions.
Neither of the above answers reliably worked to ensure a mouseup event.
Here's what I discovered works consistently:
document.querySelector('html').addEventListener('mouseup', function (e) {
console.log("html mouseup");
var evt = document.createEvent("MouseEvents");
evt.initEvent("mouseup", true, true);
document.getElementById('drag-me').dispatchEvent(evt);
});
If mouseup fires on target element, it does not fire on html, and if it did not fire on target, it will fire on html.
For above two methods:
add e.preventDefault() in mouseup event
add user-select: none CSS rule
Tried on Chrome 87 but both useless. Finally I add a additional mouseout event listener and it fired when drag out of element.
The event which you want to fire in mouseup , You can fire in mousedown and inside the function write event.stopPropagation().
It will stop the drag event.
It is going to be of help to anyone:
I recommended you use window instead of document when adding Event mouseup
The problem is in selection. Some elements are draggable, when user - having unknowingly uncollapsed selection (likely across many non-text elements), starts moving his mouse with mouse button pushed down he drags these elements alongside the movement of his mouse.
Try setting draggable="false" on html elements where mousedown event starts or if it doesn't work try clearing or collapsing selection on mousedown.
Clear:
getSelection().setPosition(null);
A bit cleaner, collapse:
const selection = getSelection();
selection.setPosition(selection.anchorNode, selection.anchorOffset)
Setting draggable="false" worked for me when working with mousedown event starting with an <img> element and clearing selection worked when I had selections spanning over multiple elements on a control panel an this made some input[type="range"] elements unusable (click event worked but not thumbnail dragging).