2

I'm trying to trigger mouseEnter event when mouse is on top of multiple elements.

I want both mouseEnter events to trigger when the mouse is at the center, and preferably for both to turn yellow.

Run the code snippet below for an example:

<!DOCTYPE html>
<html>
<head>
<style>
div:hover {
  background-color: yellow;
}
div {
  width: 100px;
  height:100px;
  background:green;
  border: 2px solid black;
}
.second {
  transform:translateX(50%) translateY(-50%);
}
</style>
<script>
  function onhover(){console.log('hovered')}
</script>
</head>
<body>

<div onmouseenter=onhover()></div>
<div onmouseenter=onhover() class='second'></div>

</body>
</html>
Aaron Meese
  • 1,670
  • 3
  • 22
  • 32
  • 1
    Great question, this one took me a while to solve! Check out my demo, it seems to solve your particular issue pretty nicely :) – Aaron Meese Aug 11 '22 at 12:54

4 Answers4

3

According to MDN, the mouseenter event does not bubble, whereas the mouseover event does. However, even if it DID bubble, your elements currently have no relation to one another, thus the mouse events are captured by the upper element.

One possible way around this is with the amazing elementsFromPoint function in JavaScript, which makes quick work of solving your issue:

// Only the IDs of the elments you are interested in
const elems = ["1", "2"];

// Modified from https://stackoverflow.com/a/71268477/6456163
window.onload = function() {
  this.addEventListener("mousemove", checkMousePosition);
};

function checkMousePosition(e) {
  // All the elements the mouse is currently overlapping with
  const _overlapped = document.elementsFromPoint(e.pageX, e.pageY);

  // Check to see if any element id matches an id in elems
  const _included = _overlapped.filter((el) => elems.includes(el.id));
  const ids = _included.map((el) => el.id);

  for (const index in elems) {
    const id = elems[index];
    const elem = document.getElementById(id);

    if (ids.includes(id)) {
      elem.style.background = "yellow";
    } else {
      elem.style.background = "green";
    }
  }
}
div {
  width: 100px;
  height: 100px;
  background: green;
  border: 2px solid black;
}
.second {
  transform: translateX(50%) translateY(-50%);
}
<div id="1"></div>
<div id="2" class="second"></div>
Aaron Meese
  • 1,670
  • 3
  • 22
  • 32
  • is there a way to prevent the mouse event from being captured? so that everything under the mouse would receive the event? – Eric Freeman Aug 11 '22 at 14:00
  • @EricFreeman what events are you having trouble receiving? AFAICT this shouldn't capture the events, it's just listening for `mousemove` and taking action when it occurs – Aaron Meese Aug 11 '22 at 14:04
  • in your answer you wrote: "thus the mouse events are captured by the upper element.", i meant to ask if its possible for every element to catch mouse events and not only the top one – Eric Freeman Aug 11 '22 at 14:35
  • another thing, your solution behaves weirdly after scrolling down abit in its container – Eric Freeman Aug 11 '22 at 14:45
  • Behaves weirdly after scrolling how? – Aaron Meese Aug 11 '22 at 15:40
  • 1
    And no, to the extend of my knowledge that is not possible. There may be a way but I was unable to find it when I was researching for my answer – Aaron Meese Aug 11 '22 at 15:41
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/247222/discussion-between-aaron-meese-and-eric-freeman). – Aaron Meese Aug 12 '22 at 11:36
1

I think that you can not without javascript, and with it it's a bit tricky, you have to check on every mousemove if the coordinates of the mouse are in de bounding box of the element, this fill fail with elements with border radius but for the others it's ok

<script>


var hovered=[]
function addHover(element){hovered.push(element)}

function onhover(element){console.log("hovered",element)}

function onCustomHover(e){
    hovered.forEach((el,i)=>{
        let bounds=el.getBoundingClientRect()
        if (e.pageX > bounds.left && e.pageX < bounds.bottom  &&
            e.pageY > bounds.top && e.pageY < bounds.right ) {            
            onhover(i);
        }
    })
}


</script>
</head>
<body>

<div id="div1"></div>
<div id="div2" class='second'></div>
<script>
    document.body.addEventListener('mousemove', onCustomHover, true);//{capture :false});
    addHover(document.getElementById("div1"))
    addHover(document.getElementById("div2"));
</script>

I would appreciate if you could rate the answer if that was usefull to you because I can not make comments yet <3

Edoldin
  • 160
  • 6
0

It will be easier to change your code a little bit.

ex. Add to your div elements class box. Add to your styles class with name hovered which will look like:

.hovered {
  background-color: yellow;
}

Into JS(between script tag) add event listeners (code not tested, but idea is shown), also move script to place before closing body tag:

const boxes = document.querySelectorAll('.box');

boxes.forEach(box => {
  box.addEventListener('mouseover', () => {
    boxes.forEach(b => b.classList.add('hovered'));
  });

  box.addEventListener('mouseout', () => {
    boxes.forEach(b => b.classList.remove('hovered'));
  });
});
vladys.bo
  • 730
  • 2
  • 11
  • 34
  • in my case all the elements are not related, I would like for all of them to trigger mouse over if the mouse is within their borders – Eric Freeman Aug 11 '22 at 14:33
  • If you can edit an HTML I don't see a problem to add some specific class to all elements which will work with "hovered" logic only, according to current question this answer provides working solution. Please provide more details of your situation – vladys.bo Aug 15 '22 at 10:18
0

The problem is that elements are blocking the mouse such that elements in the background do not receive the event. With the exception that events bubble to the parent.

Given that you could change your markup slightly to get this effect.

First add a class to your boxes so we can easily find them in JavaScript:

<div class="box"></div>
<div class="box second"></div>

Then adapt the CSS such that this background change is toggled with a class instead:

.box.hovered {
  background-color: yellow;
}

And then the JavaScript:

// Get all box elements
const boxes = document.querySelectorAll('.box');

boxes.forEach(box => {
  // For each box attach a listener to when the mouse moves
  box.addEventListener('mousemove', (ev) => {
    // Get the position of the mouse
    const { x, y } = ev;
    boxes.forEach(b => {
      // for each box get it's dimension and location
      const rect = b.getBoundingClientRect();
      // check if the pointed is in the box
      const flag = x > rect.left && x < rect.right && y > rect.top && y < rect.bottom;
      // toggle the class
      b.classList.toggle('hovered', flag);
    });
  });
});

This can be improved a lot, especially if you have more boxes by getting the rectangles beforehand and then using the index in the forEach to link the box to it's rectangle:

const boxes = document.querySelectorAll('.box');
const rects = [...boxes].map(box => box.getBoundingClientRect());

Another improvement is to use the fact that events bubble to the parent, that means you could wrap all boxes in one parent and only add a listener to this parent.

Stephane Vanraes
  • 14,343
  • 2
  • 23
  • 41
  • any idea about how to do this with unrelated elements? so that they all catch mouse event if there's a mouse on top of them? – Eric Freeman Aug 11 '22 at 14:32