2
votes

I have HTML that looks like this:

<div id="parent">
  <div class="child">
    Child 1
  </div>
  <div class="child">
    Child 2
  </div>
  <div class="child">
    Child 3
  </div>
</div>

I attach mouseenter and mouseout events like so:

$("*").on("mouseenter", function() {
    $(this).addClass("mouse_over");
});

$("*").on("mouseout", function() {
    $(this).removeClass("mouse_over");
});

Now, imagine the following mouse sequence of events:

  1. I move the mouse into the parent div. As expected the mouseenter event fires and the mouse_over class is put on the parent.
  2. I move into one of the children. The mouseout event fires for the parent, and the mouseenter event fires for the child. Good.
  3. I move the mouse back into the parent. The mouseenter DOES NOT fire here.

The third step is the issue here. I want the mouse_over class to be put back on the parent when reentering the parent from its child element.

I think I understand why this is happening, as technically my mouse is in the parent the whole time so firing the mouseenter event does not make sense.

Here's a fiddle better illustrating what I'm trying to do:

https://jsfiddle.net/tg1wg1xx/

If you hover into the parent and children elements, you'll notice on your way out the pattern overlay is not placed on the parent.

So how can I ensure that the overlay is always placed on whatever element I am currently hovering over?

3
Is that what you want? jsfiddle.net/tg1wg1xx/7 - Karl-André Gagnon
@Karl-AndréGagnon Yes, that's exactly what I was looking for! Post an answer and I'll accept it. - thatidiotguy

3 Answers

8
votes

As Boris explained in his answer, there's 2 different events. To quote him :

  • mouseenter and mouseleave are triggered when you enter and leave a hierarchy of nodes, but not when you navigate that hierarchy's descendance.
  • mouseover and mouseout are triggered when the mouse respectively enters and leaves a node's "exclusive" space, so you get a "out" when the mouse gets into a child node.

That being said, you need mouseover and mouseout since you need to trigger the mouse out when hovering a child.

$("*").on("mouseover", function(e) {
    $(this).addClass("mouse_over");
});

$("*").on("mouseout", function(e) {
    $(this).removeClass("mouse_over");
});

Next, you need to know that event bubble in the tree. So when you mouseover on a child, the event is also propagated to the parents. That's why the parent is in hover state even when hovering a child.

To solve that, you need to use .stopPropagation() on the event object.

$("*").on("mouseover", function(e) {
    $(this).addClass("mouse_over");
    e.stopPropagation();
});

$("*").on("mouseout", function(e) {
    $(this).removeClass("mouse_over");
    e.stopPropagation();
});

See it in action


Bonus

In the spirit of writing less code, you can also use that :

$("*").on("mouseover mouseout", function(e) {
    $(this).toggleClass("mouse_over");
    e.stopPropagation();
});
7
votes

mouse events in javascript can be disorienting. Please note that there are two pairs of events that don't mean the same thing :

  • mouseenter and mouseleave are triggered when you enter and leave a hierarchy of nodes, but not when you navigate that hierarchy's descendance.
  • mouseover and mouseout are triggered when the mouse respectively enters and leaves a node's "exclusive" space, so you get a "out" when the mouse gets into a child node.

the behavior you are observing is because you are mixing the two : I updated your fiddle here https://jsfiddle.net/drxrea7e/1/ to use enter and leave.

you can refer to the events' documentation on mozilla's site for detailed info.

Edit after your comment: ah then we need another thing :

the event actually bubbles, so the parent also receives it (but with target = theChild). So you want to add the class only if event.target == this :see my new version of the fiddle : https://jsfiddle.net/drxrea7e/3/.

The children get the image from the class .mouse_over AND the red color from .parent, because background:url() overwrites background-color:white. Is that what you expected?

3
votes

Made some updates in your code to make it work as you wanted.

$("*").on("mouseover", function(e) {
	$(this).addClass("mouse_over");
  e.stopPropagation();
});

$("*").on("mouseout", function(e) {
	$(this).removeClass("mouse_over");
  e.stopPropagation();
});
#parent {
  padding: 24px;
  background-color:red;
}

.child {
  padding: 6px;
  background-color:white;
}

.mouse_over {
    background:url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAIUlEQVQYV2NkYGBIY2BgmMUABYxQGi4IEwCJgwWRBcCCAHscA2vMYjzKAAAAAElFTkSuQmCC
    ) repeat !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent">
  <div class="child">
    Child 1
  </div>
  <div class="child">
    Child 2
  </div>
  <div class="child">
    Child 3
  </div>
</div>