49

I need to know when the mouse cursor leaves a div. So I hook up the mouseout event. However, if I move the mouse very quickly out of the div, the mouseout event doesn't fire. That's right: the mouse cursor was sitting still inside the div, it's now outside the div, and yet the mouseout callback hasn't been called. (It works fine if I don't move the mouse quite so fast.)

This is true in the latest Google Chrome by the way – so not just an "old browsers" problem.

A workaround:

A question about this problem has been posed before. Apparently it's just a fact of life, and the only workaround I've found is to manually monitor mousemove events, each time checking the cursor's x/y co-ordinates and seeing if they fall within the div’s bounding box, so you have more chances to "notice" if the cursor is no longer inside it.

Compared to letting the browser do all this natively, performing calculations on every pixel move is a bit of a performance hit. It's also tedious to code.

On to my question...

Why can't the browser can't reliably capture the mouseout event? If I can reliably tell when the mouse has left the div using the above workaround, why can't the browser do it?

I understand (from the answer linked above) that JavaScript doesn't try to interpolate "frames". Say if you put a mousemove handler on the document, and quickly move the mouse 200 pixels to the right in a perfect horizontal line, you might not get 200 mousemove events. A few will be missed. I don't have a problem with that.

But if some pixel movements are missed just as the mouse crosses the boundary of the div, why does it follow that the mouseout event should also be skipped? When the browser finally starts registering the mouse's position again (after a sudden fast movement), even if the mouse is now miles outside the box, the point is that it used to be in the box and no longer is. So why doesn't it then fire the mouseout event then?

I just don't get why this would be a hard problem for the browser vendors to solve. (But I trust there might be a good reason, which I'm too stupid to think of.)

I'm posting this question mainly out of curiosity, but I'm hoping the answer might give some insight that could help me work around the problem more efficiently. Also, any alternative workarounds (which are faster than the one presented above) would be welcome.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
callum
  • 34,206
  • 35
  • 106
  • 163
  • 1
    This is of absolutely no help to you (hence a comment, rather than an answer), but that behavior most definitely sounds like a bug. That's also probably obvious, but to address your question "why does it follow that the mouseout event should also be skipped?", the answer would he that *it doesn't*, as it's non-intentional behavior. – jmar777 Sep 16 '11 at 17:58
  • Interesting. That is a lot of help to me actually – if you're right that it's a bug. Seems odd to me that this kind of bug would exist in Chrome 15 though! I'm going to make a test page and try it in other browsers and see how widespread it is. And I might submit a bug report to crbug.com and see what they make of it. Thanks. – callum Sep 16 '11 at 18:04
  • FYI: Chrome 15 is not in the stable channel yet – Candide Sep 16 '11 at 18:34
  • @Ingenu: true, bad example. But it's also the case with Chrome 11-14 (and presumably earlier versions) . I'm going to see if it's the case in other browsers (I'll have to make a new test page first, as my example is in a Chrome extension). – callum Sep 16 '11 at 20:09
  • A workaround that will always work (though which is not the best) is using `setInterval`. No matter what events get fired or what quirks the browser has, you can simply check every so many milliseconds. – pimvdb Sep 16 '11 at 20:39
  • Because you need `mouseleave`, but its not implemented in Gecko and Webkit http://www.quirksmode.org/dom/events/#t017 – c69 Sep 18 '11 at 06:29
  • Could you post a bare-minimum page that demonstrates this problem? – s4y Sep 20 '11 at 15:49
  • P.S. The question you linked to relates to never getting a `mouseover` *or* a `mouseout` when the mouse moves over an element very quickly (i.e. the browser never considered the cursor to be “over” it). In your tests, does the cursor start off inside the element? – s4y Sep 20 '11 at 16:03
  • I use jQuery heavily and write code that does what you describe quite often using .mouseleave http://api.jquery.com/mouseleave/ You might try it out. – Gunnar Hoffman Sep 17 '11 at 23:05

6 Answers6

9

I know that you don't want a workaround, but you don't need to check mouse's x/y to know if you are in or out an element. You could simply check the element from which the mousemove event was fired. If you put a mousemove on document, the event will fire from one of its children, and you can compare that element with your element to know if it is one of its descendants.

Or you could go up the parentNode tree and stop if you find your element. Then you know you are inside the element and still in it, otherwise you reach the document and you are out.

Some browsers implement the mouseenter/mouseleave events that, I've noticed, are more accurate than mouseout. Prototype and jQuery have a workaround for browsers that don't implement these new events. Mouseleave does not fire from an element's children, whereas mouseout does.

5

You describe moving the mouse very quickly. When you stop, is the pointer still within the page? That is, is your mouse pointer still hovering over some part of the visible web page?

If it has gone outside, then it's not necessarily clear what the browser should do. The mouseout event should have a relatedTarget property that targets what the mouse pointer has gone into. If the mouse pointer is already outside of the page area, there would be no related target to point to.

Put another way, when the mouse leaves the page area, the browser stops tracking it and stops reporting its position. If you move your mouse fast enough, from the browser's perspective, the mouse simply disappeared. It's not until you bring the mouse back into the bounding box of the viewable page that the browser knows where it is, and then triggers all appropriate movement-based actions (like mouseout).

jimbo
  • 11,004
  • 6
  • 29
  • 46
1

I found your question and the lack of other clear answers useful because it told me that I had to create a workaround. Which I did using the ideas presented in your question and the other contributors.

I have same problem when I use jquery mouseleave elem.bind('mouseleave', data, mouseLeavesZone);

The problem is intermittent and may be related to a busy CPU on the client. Say, for example, the CPU is busy elsewhere when your mouse moves out of a div. Then it seems logical that this could be the cause of the bug. I agree; this should be fixed by the browser vendors.

See http://jsfiddle.net/bgil2012/gWP5x/1/

(Aside: My JQuery code needs to use older jQuery methods because it has to run in Drupal 7 which is running jQuery 1.4, at this time and without applying patches that are coming).

Bryan
  • 1,103
  • 12
  • 16
1

I ran into this problem a few times and I came to accept the issue as a fact of life. But depend on your needs, you can just use CSS as I did. For example, if I just want to show/hide an element base on another element got hovered, then CSS is the way to go. Here is a working, reliable example:

.large {
  width: 175px; height: 175px;
  position: absolute;
  border-radius: 100%;

  /*hide the glass by default*/
  top: -9999px;
  left: -9999px;
  opacity: 0;
  transition: opacity .2s ease-in-out;
  z-index: 100;
  pointer-events: none;
}

.small:hover + .large {
  opacity: 1;
}

http://codepen.io/tanduong/pen/aBMxyd

Tan Duong
  • 1,473
  • 2
  • 17
  • 29
1
  1. Why can't the browser can't reliably capture the mouseout event? If I can reliably tell when the mouse has left the div using the above workaround, why can't the browser do it?

    I think you answered this one yourself when you said:

    Compared to letting the browser do all this natively, performing calculations on every pixel move is a bit of a performance hit.

    The browser does not interpolate between the frames, thus, as you stated it would demand a lot more resources and memory, which may be why it isn't "fixed".

  2. If some pixel movements are missed just as the mouse crosses the boundary of the div, why does it follow that the mouseout event should also be skipped? When the browser finally starts registering the mouse's position again (after a sudden fast movement), even if the mouse is now miles outside the box, the point is that it used to be in the box and no longer is. So why doesn't it then fire the mouseout event then?

    I don't know for sure, but I don't think it's a condition of "it was in and now it's out". Instead, it's whether it crosses that boundary (if MouseX - ElemOffsetX= 1). I agree, it doesn't make as much sense, but it could be because if you set the value to > 1 it would trigger the event multiple times. Otherwise it would have to keep track of the events, which is not in JS nature, seeing how it just adds events asynch to a stack.


What you could try is using jQuery's mouseleave event. This does two things, which delays the firing of the event:

  1. It traverses the DOM tree to see if it truly left the element
  2. I think it implements a timeout call, which should solve the interpolation problem that you noticed.
vol7ron
  • 40,809
  • 21
  • 119
  • 172
0

Here's a simple workaround.

In your onMouseOver listener, you can add a 'mousemove' listener to the window:

<div onMouseOver={() => {
    setMouseOver(true)

    let checkMouseLeave = (e: MouseEvent) => {
        if (rootRef.current
            && !rootRef.current.contains(e.target as HTMLElement)) {
            setMouseOver(false)
            window.removeEventListener('mousemove', checkMouseLeave)
        }
    }

    window.addEventListener('mousemove', checkMouseLeave)
}
></div>

Then you can check on each mouse move until the mouse is outside of your div (rootRef.current in our example).

jahooma
  • 653
  • 7
  • 7