7

Red square is the part of a container with class "parent". If I hover mouse over that red square it disappears. But why? I expected that it shouldn't.

Expected behaviour: it does not disappear since red square is a part of ".parent" container and I have clearly stated, that the mouseout event occurs on that container.

There was a suggestion, that this question is a duplicate of

JavaScript mouseover/mouseout issue with child element

In some way - yes, but I think that this question provides value, because it not only provides the solution ("you can try this"), but also explains WHY you should use that and WHY the initial solution is not working as it is supposed to.

<span class="parent">Hover mouse over this text<br></span>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
    function removeSquare()
    {
        $(this).find(".kvadrat").remove();
    }

    function addSquare()
    {
        $(this).append("<span style='display:inline-block;width: 50px;height: 50px;background-color:red' class='kvadrat'></span>");
        $(this).on("mouseout", removeSquare);
    }
    $(".parent").on("mouseover", addSquare);
</script>
Community
  • 1
  • 1
Jaroslav Tavgen
  • 312
  • 1
  • 9
  • You have used $(this).on("mouseout", removeSquare); that is why it is disappearig – Nimish May 21 '17 at 11:19
  • But it shouldn't. Square is the part of $(this). – Jaroslav Tavgen May 21 '17 at 11:23
  • 1
    BTW, your code has another problem, too: every time the `mouseover` event fires, you're adding another copy of the `mouseout` handler. If you keep moving the mouse in and out of the parent element, you'll eventually end up with dozens of redundant `mouseout` handlers on it. – Ilmari Karonen May 21 '17 at 18:41

5 Answers5

11

It's normal behaviour of .mouseout() event.

Show the number of times mouseout and mouseleave events are triggered. mouseout fires when the pointer moves out of the child element as well, while mouseleave fires only when the pointer moves out of the bound element.

You should use .mouseenter() and .mouseleave() events,

function removeSquare()
{
$(this).find(".kvadrat").remove();
}

function addSquare()
{
    $(this).append ( "<span style='display:inline-block;width: 50px;height: 50px;background-color:red' class='kvadrat'></span>" );
    
}
$ ( ".parent" ).on ( "mouseenter", addSquare );
$(".parent").on("mouseleave", removeSquare);
.parent {
  display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="parent">Hover mouse over this text<br></span>
Okan Kocyigit
  • 13,203
  • 18
  • 70
  • 129
  • Square does not dissappear even if I hover the mouse to the right of the square(outside of it) at the same row – Jaroslav Tavgen May 21 '17 at 11:29
  • I've played with css to see what's going on. check the updated jsfiddle https://jsfiddle.net/Lp3L7dp6/3 – Okan Kocyigit May 21 '17 at 11:30
  • I don't get the downvote, this is the correct explanation. – Gerardo Furtado May 21 '17 at 11:32
  • ocanal, that's pretty good solution, but if you hover mouse very slowly, then you will see, that the square still disappears. – Jaroslav Tavgen May 21 '17 at 11:34
  • @JaroslavTavgen, there is another problem actually, because you putting an inline-blocked element in inline element is not valid. So your span must be inline-block also. – Okan Kocyigit May 21 '17 at 11:36
  • Why it is not valid? – Jaroslav Tavgen May 21 '17 at 11:37
  • check [this](https://jsfiddle.net/Lp3L7dp6/4/), you will see a gap between box and text, that's why a problem appear when you move the cursor slowly and don't use inline-block for .parent. It's not valid because inline element can't cover a blocked element.http://stackoverflow.com/questions/9089953/can-you-have-span-within-span – Okan Kocyigit May 21 '17 at 11:43
  • I have tried your solution on my project, and it works! Thank you very much for the explaination! Why it is so rarely explained, that mouseover and mouseout have such weird and not very logical feature? – Jaroslav Tavgen May 21 '17 at 11:45
  • @JaroslavTavgen from jQuery doc: `[.mouseover()] can cause many headaches due to event bubbling. For instance, when the mouse pointer moves over the Inner element in this example, a mouseover event will be sent to that, then trickle up to Outer. This can trigger our bound mouseover handler at inopportune times. See the discussion for .mouseenter() for a useful alternative` http://api.jquery.com/mouseover/ It is explained and demonstrated. The doc just needs to be read :) – quirimmo May 21 '17 at 12:23
  • you have the same behavior when using the standard JavaScript `addEventListener`. You can use the third parameter to specify if you want to use `capturing` or `bubbling` mode: https://developer.mozilla.org/it/docs/Web/API/Element/addEventListener – quirimmo May 21 '17 at 12:25
6

As other people have noted, your original problem is that mouseover and mouseout events also fire for child elements. The solution to that issue is either to use jQuery's mouseenter and mouseleave events, or simply to replace the JS code with the CSS :hover pseudo-class.

However, the reason why the other JS and CSS solutions posted here sometimes behave erratically (causing the square to disappear if you move the mouse over it slowly, but not if you move it fast, and not on all browsers even if you move it slowly) is because, depending on your browser and font settings, there may or may not be a small gap between the top line of text and the square below it. If the gap exists, and your mouse cursor hits it while moving from the text to the square, the browser will consider the mouse to have left the parent element, and will thus hide the square.

Setting a (light blue) background color on the parent element shows the issue clearly; depending on what font and line height the browser chooses, the parent element and the box can look like this:

Screenshot (small line height, no gaps between lines)

or like this:

Screenshot (large line height with gaps between lines)

Manually setting a particularly large line height makes the problem easily reproducible (CSS example based on Thomas van Broekhoven's answer):

.kvadrat {
  display: none;
}
.parent:hover > .kvadrat {
  display: inline-block;
  background-color: red;
  width: 50px; height: 50px;
}
.parent {
  line-height: 2.0;
  background: lightblue;
}
<span class="parent">Hover mouse over this text!<br>
Here's another line of text.<br>
<span class='kvadrat'></span></span>

There are two general ways to fix this issue. The simplest option, where practical, is to make the parent element a block, thereby eliminating the gaps between the lines. You may also wish to add position: absolute to the square's style, so that it won't expand its parent element when it appears:

.kvadrat {
  display: none;
}
.parent:hover > .kvadrat {
  display: inline-block;
  position: absolute;
  background-color: red;
  width: 50px; height: 50px;
}
.parent {
  display: block;
  line-height: 2.0;
  background: lightblue;
}
<span class="parent">Hover mouse over this text!<br>
Here's another line of text.<br>
<span class='kvadrat'></span></span>

Alternatively, if you really want to stick with an inline parent element (e.g. because you want it to be able to wrap across several lines of text), you can set a negative top margin on the square to make sure it overlaps the line of text above it. If you don't want the square to visibly overlap the text, you can further move all the visible content of the square into an inner element and set a corresponding positive top margin on it, like this:

.kvadrat {
  display: none;
}
.parent:hover > .kvadrat {
  display: inline-block;
  position: absolute;
  margin-top: -1em;
  border: 1px dashed gray; /* to show the extent of this otherwise invisible element */
}
.kvadrat > .inner {
  display: block;
  margin-top: 1em;
  background-color: red;
  width: 50px; height: 50px;
}
.parent {
  line-height: 2.0;
  background: lightblue;
}
<span class="parent">Hover mouse over this text!<br>
Here's another line of text.<br>
<span class='kvadrat'><span class='inner'></span></span></span>
Community
  • 1
  • 1
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
5

I know this is not directly answering your JavaScript question, but I would like to open your eyes if you're not bounded to JavaScript. You can easily achieve this with CSS.

.kvadrat {
  display: none:

}
.parent:hover > .kvadrat {
  display: inline-block;
  background-color: red;
  width: 50px;height: 50px;
}
<span class="parent">Hover mouse over this text<br>
<span class='kvadrat'></span></span>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • Thank you very much for the solution! But it does not cancel the question, though, as there is something that I clearly do not understand about Javascript. – Jaroslav Tavgen May 21 '17 at 11:25
  • I like your approach, but there is the problem: if you hover the mouse from the text to the square slowly, then the square disappears. This is the reason why I have rejected that approach in the past. – Jaroslav Tavgen May 21 '17 at 11:31
  • 1
    If your mouse enters the red square at the top, this is not happening, right? Only if you try to enter the square from the right side, at which you basically stopped hovering the text. – Thomas van Broekhoven May 21 '17 at 13:08
  • 1
    @Thomas: It may or may not happen even when entering from the top, depending on your browser and its font settings. You can almost certainly trigger it by adding, say, `.parent { line-height: 2 }` to the CSS. The fix is either to make the `.parent` element a block (so that it can't have any gaps between lines) or to set a sufficient negative top margin on the square to make sure it always overlaps the text (possibly with the actual visible content of the square moved into an inner element with a corresponding positive top margin). – Ilmari Karonen May 21 '17 at 14:08
  • 1
    Did not consider that, but it can indeed be fixed. Thanks! – Thomas van Broekhoven May 21 '17 at 14:12
1

You can achieve the same using CSS.

.child {
  display: none:
  
}
.parent:hover > .child {
  display: inline-block;
  background-color: red;
  width: 50px;
  height: 50px;
}
<span class="parent">Hover mouse over this text<br>
  <span class='child'></span>
</span>
Milan Chheda
  • 8,159
  • 3
  • 20
  • 35
0

It is because of event bubbling. When you enter the child span, you jQuery will fire mouseout because you've now gone to a child span. If you want to keep it going, use mouseenter and louseleave which does not fire until you leave the actual element, regardless of child elements.

<span class="parent">Hover mouse over this text<br></span>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
function removeSquare()
{
$(this).find(".kvadrat").remove();
}

function addSquare()
{
    $(this).append ( "<span style='display:inline-block;width: 50px;height: 50px;background-color:red' class='kvadrat'></span>" );
    $(this).on("mouseleave", removeSquare);
}
$ ( ".parent" ).on ( "mouseenter", addSquare );
</script>
ChrisD
  • 147
  • 5