Moving the mouse: mouseover/out, mouseenter/leave

In this chapter, we are going to explore what comes about when the mouse moves between elements. Let’s start to dive into details about what mouseover, mouseout, mouseenter and mouseleave events are and how they operate.

Events mouseover/mouseout, relatedTarget

The mouseover event takes place when the pointer of the mouse comes over an element. On the contrary, the mouseout event occurs when it leaves.

These events are considered specific, as they include the relatedTarget property. The relatedTarget property complements target. After a mouse leaves an element for another, one of them transforms into the target, the other one- into the relatedTarget.

So, for mouseover:

  • the element where the mouse came over is the event.target .
  • the element from which the mouse came is the event.relatedTarget.(relatedTarget → target)

For mouseout, we have the opposite situation:

  • the element that the mouse left is the event.target .
  • the new under-the-point element the mouse left for is the event.relatedTarget (target → relatedTarget.

Please, note that relatedTarget can be null.

It can be considered typical and means that the mouse didn’t come from another element but out of the window. So, it would be best if you kept that fact in mind while using event.relatedTarget in your code. If you try accessing event.relatedTarget.tagName an error will occur.

Skipping Elements

The mouseover element gets activated when the mouse moves. But it doesn’t mean that each pixel will lead to an event. Time to time the mouse position is checked by the browser. As a result, whenever a change is noticed, events are triggered. It also means that if the visitor moves the mouse very fast, several DOM-elements might be skipped, like this:

In the picture above, if the mouse moves too fast from “FROM” to “TO”, then the intermediate <div> elements might be skipped. The mouseout event will trigger on “FROM” and then directly on “TO”. In practice, it is quite handy because there may be too many intermediate elements that you don’t wish to go through.

Please, take into consideration the mouse pointer doesn’t always go through all the elements on the way. That is possible that the pointer jumps right inside the middle of the page from the outside of the window. In a case like this, relatedTarget is null, as it’s not known where exactly it came from, like here:

You can easily check it out here:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <style>
      #parent {
        background: #008000;
        width: 200px;
        height: 140px;
        position: relative;
      }      
      #child {
        background: #0000FF;
        width: 50%;
        height: 50%;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }      
      textarea {
        height: 200px;
        width: 500px;
        display: block;
      }
    </style>
  </head>
  <body>
    <div id="parent">
      Parent
      <div id="child">Child</div>
    </div>
    <textarea id="text"></textarea>
    <input onclick="clearText()" value="Clear" type="button">
    <script>
      let parent = document.getElementById('parent');
      parent.onmouseover = parent.onmouseout = parent.onmousemove = handler;
      function handler(event) {
        let type = event.type;
        while(type < 11) type += ' ';
        log(type + " target=" + event.target.id)
        return false;
      }
      function clearText() {
        text.value = "";
        lastMessage = "";
      }
      let lastMessageTime = 0;
      let lastMessage = "";
      let repeatCounter = 1;
      function log(message) {
        if(lastMessageTime == 0) lastMessageTime = new Date();
        let time = new Date();
        if(time - lastMessageTime > 500) {
          message = '------------------------------\n' + message;
        }
        if(message === lastMessage) {
          repeatCounter++;
          if(repeatCounter == 2) {
            text.value = text.value.trim() + ' x 2\n';
          } else {
            text.value = text.value.slice(0, text.value.lastIndexOf('x') + 1) + repeatCounter + "\n";
          }
        } else {
          repeatCounter = 1;
          text.value += message + "\n";
        }
        text.scrollTop = text.scrollHeight;
        lastMessageTime = time;
        lastMessage = message;
      }
    </script>
  </body>
</html>

An important note: One thing is sure that if the pointer has entered an element (mouseover), the mouseout will also be implemented.

Mouseout when Leaving for a Child

Another important mouseout feature is that it triggers at the time the pointer moves from an element to its descendant. In other words, when it moves from #parent to #child, like in the HTML example below:

<div id="parent">
  <div id="child">...</div>
</div>

When you are on #parent, then move the pointer to the #child, you will get mouseout on the #parent. That can seem strange at first sight but it has an explanation. In pursuance of the browser logic, the cursor of the mouse can be over a single element at any time. So, in case it goes to another element, it leaves the former one.

Another important detail of the event proceeding is the following: the mouseover event on a descendant can bubble up, and if the #parent has a mouseover handler, it will trigger.

Moving the mouse from #parent to #child will bring two events on #parent, like here:

  1. mouseout [target: parent] (left the parent), after
  2. mouseover [target: child] (reached the child, bubbled).
<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <style>
      #parent {
        background: #008000;
        width: 200px;
        height: 140px;
        position: relative;
      }
      #child {
        background: #0000FF;
        width: 50%;
        height: 50%;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
      textarea {
        height: 200px;
        width: 500px;
        display: block;
      }
    </style>
  </head>
  <body>
    <div id="parent" onmouseover="mouselog(event)" onmouseout="mouselog(event)">
      Parent
      <div id="child">Child</div>
    </div>
    <textarea id="text"></textarea>
    <input type="button" onclick="text.value=''" value="Clear">
    <script>
      function mouselog(event) {
        let d = new Date();
        text.value += `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()} | ${event.type} [target: ${event.target.id}]\n`.replace(/(:|^)(\d\D)/, '$10$2');
        text.scrollTop = text.scrollHeight;
      }
    </script>
  </body>
</html>

So, at the moment the pointer moves from #parent element to #child, on the parent element, mouseover, and mouseout trigger, as follows:

parent.onmouseout = function (event) {
  /* event.target: parent element */
};
parent.onmouseover = function (event) {
  /* event.target: child element  */
};

Events mouseenter and mouseleave

Other events, such as mouseenter and mouseleave are similar to mouseover and mouseout. They also trigger at the moment the mouse pointer enters or leaves the element.

But, they are not the same as the events above. At least two essential differences can be pointed out:

  • The transitions inside the element are not counted.
  • These events (mouseenter, mouseleave) never bubble.

These events are simpler than the previous ones.

At the moment the pointer enters an element, the mouseenter event triggers. The specific location of the pointer or its descendant doesn’t matter. As for the mouseleave, it triggers when the pointer leaves an element.

So, the only generated events are the ones connected to moving to moving the pointer in and out of the top element. Nothing will happen in case the pointer moves to the child and back. Any transitions between descendants will be dismissed:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <style>
      #parent {
        background: #008000;
        width: 200px;
        height: 140px;
        position: relative;
      }
      #child {
        background: #0000FF;
        width: 50%;
        height: 50%;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
      textarea {
        height: 200px;
        width: 500px;
        display: block;
      }
    </style>
  </head>
  <body>
    <div id="parent" onmouseenter="mouselog(event)" onmouseleave="mouselog(event)">
      Parent
      <div id="child">Child</div>
    </div>
    <textarea id="text"></textarea>
    <input type="button" onclick="text.value=''" value="Clear">
    <script>
      function mouselog(event) {
        let d = new Date();
        text.value += `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()} | ${event.type} [target: ${event.target.id}]\n`.replace(/(:|^)(\d\D)/, '$10$2');
        text.scrollTop = text.scrollHeight;
      }
    </script>
  </body>
</html>

Summary

In this chapter, we dived deeper into the events in JavaScript. We explored the events such as mousemove, mouseover, mouseout, mouseenter and mouseleave.

The following things are especially handy:

  • A fast mouse move skips intermediate elements.
  • The mouseover/out and mouseenter/leave events include an additional useful property called relatedTarget.

The mouseover/mouseout events can trigger even when moving from the parent element to a child. Your browser assumes that the mouse may only be over a single element at one time.

In that aspect, the mouseenter/mouseleave events differ from the events above. They can trigger only when the mouse moves in and out of the element as a whole. Another significant thing to remember: the mouseenter/mouseleave events don’t bubble.




Do you find this helpful?

Related articles