1

There is a piece of code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Events Examples</title>
    <style>
        ul {
            width: 200px;
            height: 200px;
            margin: 10px;
            background-color: #ccc;
            float: left;
        }

        .highlight {
            background-color: red;
        }
    </style>
</head>
<body>
<ul id="list">
    <li>First</li>
    <li>Second</li>
    <li>Third</li>
</ul>
<script>
    const ul = document.querySelector('#list');
    ul.addEventListener('mouseover', highlight);
    ul.addEventListener('mouseout', highlight);

    function highlight(event) {
        console.log(event.target);
        event.target.classList.toggle('highlight');
    }
</script>
</body>
</html>

When started, it looks like this:

enter image description here

What I expect from listeners. When I move the mouse over the grey zone of 'ul', then highlight() function should work. Well, it works fine. What I don't understand: when I move the mouse over 'li' elements, then highlight() works again for unknown reason. How can it be fixed? I'm new to JS and I have not found the answer to the problem described.

enter image description here enter image description here

sva605
  • 1,571
  • 3
  • 20
  • 34

3 Answers3

2

If you're saying you only want to highlight the entire region, then use "mouseenter" and "mouseleave" instead, and this to reference the element.

Then there's no event bubbling issue to have to deal with.

const ul = document.querySelector('#list');
ul.addEventListener('mouseenter', highlight);
ul.addEventListener('mouseleave', highlight);

function highlight(event) {
  this.classList.toggle('highlight');
}
ul {
  width: 200px;
  height: 200px;
  margin: 10px;
  background-color: #ccc;
  float: left;
}

.highlight {
  background-color: red;
}
<ul id="list">
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
</ul>
1

Events always bubble up, but if you want to select the element that you actually added the listener to, use event.currentTarget instead of event.target.

function highlight(event) {
    console.log(event.currentTarget);
    event.currentTarget.classList.toggle('highlight');
}

Information on event bubbling: What is event bubbling and capturing?

If you want to be sure that the event only gets called on the element that you registered it on, you can check if target matches currentTarget.

tech4him
  • 970
  • 5
  • 20
  • Wouldn't `highlight` still be called twice though? Once when entering or exiting the `ul` and again when entering or exiting `li`..? –  Dec 17 '17 at 19:34
  • 'if (event.target !== event.currentTarget) return;' — this line should be deleted – sva605 Dec 17 '17 at 19:46
  • Why? That was added to fix the problem @Terminus mentioned above. – tech4him Dec 17 '17 at 19:53
  • Without this line I have the behavior I wanted: the rectangle is red while the cursor is inside it. But with this line when I move the mouse over 'li', the rectangle gets grey again. I need it to remain red until I move the mouse beyond its outer ages. – sva605 Dec 17 '17 at 19:56
  • OP has a point. If the `if ...` is removed, then the whole `ul` is highlighted, even while mousing over the `li`s. With the `if`, mousing into the `ul` highlights it but, mousing into the `li`s removes the highlight. [see fiddle](https://jsfiddle.net/L6a4uemk/)(compare and contrast with the `if` commented). –  Dec 17 '17 at 20:14
  • 2
    That's because when mousing over the `li`, the `mouseout` takes place on the `ul`, even though you're still within its boundaries. To do it with a bubbling event and without having redundant class setting, you'd need to involve `event.relatedTarget`, but that gets a little tricky. –  Dec 17 '17 at 20:17
  • @rockstar Good point. In other words, when you move the mouse over a list item, a `mouseout` gets called on the `ul`, then a `mouseover` gets called on the `li`. That seems like it could possibly cause a flash/race condition. In that case, your answer is probably better. – tech4him Dec 17 '17 at 21:08
  • 2
    Ultimately, the best solution would be to do this with CSS instead of JS by using `ul:hover { ... }` –  Dec 17 '17 at 21:58
  • Completely agree, if that is the entire point of the OP, that's what I would recommend. – tech4him Dec 17 '17 at 22:25
1

If you mouseover one element contained within another, the "inner" element (in your case the <li> will fire a mouseover event, and this will "bubble" up to the element where you attached the listener. The target property on the event will be the inner element that triggered the event, not the one where you attached the listener.

Rather than target, use currentTarget, which indicates the element that you attached the listener to.

update As @Terminus points out, this could lead to multiple handlings of the event, since mousing over both the li and the ul will trigger mouseover events that get handled by the listener. The solution would then be to only run the code if the target is the currentTarget

if(event.target === event.currentTarget)
     event.currentTarget.classList.toggle('highlight');
Faust
  • 15,130
  • 9
  • 54
  • 111
  • 2
    Wouldn't `highlight` still be called twice though? Once when entering or exiting the `ul` and again when entering or exiting `li`..? –  Dec 17 '17 at 19:35