or... What's going on with my mouseout/mouseover events?

If I had a penny for every time I've seen a post in a forum from someone wondering why their mouseout script isn't working properly I'd have an awful lot of pennys. The function provided on this page, isMouseLeaveOrEnter, will fix about 95% of the problems people have.


The problem encountered by most is that the mouseout and mouseover events are fired a lot more often than you might think. Here's a couple of examples that demonstrate the issue.


In this example the counters are updated on every mouseout or mouseover event. Notice that a number of mouseouts are fired as you move over the nested div.

Try moving the mouse in and out of this div
and the nested div

mouseovers mouseouts

Mouseover/Mouseout with the isMouseLeaveOrEnter

In this example the counters are only updated once the isMouseLeaveOrEnter function checks that the mouse has actually move over or away from the parent element. Now moving over the nested element no longer triggers anything.

Try moving the mouse in and out of this div
and the nested div

mouseenters mouseleaves

Why is this happenning?

I'm not sure whether this matches what happens behind the scenes or not, but this is the way I think about events in javascript.

An event in javascript is always thrown. As you move the mouse around a page you'll fire off a number of mouseovers, mouseouts & mousemoves. Most of the time there are no handlers for these events and so nothing happens. Once you start listening for these events you need to be aware of event bubbling.

Event bubbling refers to the way events are propogated. If you move the mouse over an element you will trigger a mouseover for that element. If the element has a handler for that event it will fire. Once that event handler has finished the event will then be passed to the parent element. If the parent has a handler it will then fire and upon finishing will pass the the event to its parent and so on until the event reaches the document object.

What this means is that when you declare a handler for an event on an element you need to be aware that it will catch not only events fired by the element itself, but also events fired by any of it's children. A very common complaint from people trying to build menu systems is that the menu flickers or dissappears at inappropriate times. This is almost always caused by event bubbling. A handler for the mouseout event is attached to the menu so that it will dissappear when the user moves the mouse off it.

What you need to think about is when will this event handler catch an event? The answer is 'A lot more times than you want!'. A mouseout will fire whenever you move the mouse from the parent element to one of it's children or outside the element. Because of event bubbling it will also catch any mouseouts from it's child elements.

So how do I fix it?

Under internet explorer there are 2 events you don't see in other browsers, mouseleave and mouseenter. These 2 events operate the way most people think mouseout and mouseover work until they find themselves in difficulty. They fire only when the mouse enters or leaves the element and ignores all of the elements children. If they weren't microsoft only this article would never have been written.

The only cross-browser tools available are mouseout & mouseleave so we'll work with them. What we need to do is check that these events are not being fired because of interactions with the elements children. To do this I've written the following function.

// this function determines whether the event is the equivalent of the microsoft // mouseleave or mouseenter events. function isMouseLeaveOrEnter(e, handler) { if (e.type != 'mouseout' && e.type != 'mouseover') return false; var reltg = e.relatedTarget ? e.relatedTarget : e.type == 'mouseout' ? e.toElement : e.fromElement; while (reltg && reltg != handler) reltg = reltg.parentNode; return (reltg != handler); }

If you use this script it would be friendly to provide a link back to this site.

isMouseLeaveOrEnter explained

The function essentially checks that the related element for the event was not a child of the event handler or the event handler itself.

The function takes 2 parameters. The event (e) & the event handler (handler).

The 1st line checks that the event is a mouseout or a mouseover, if not the function returns false and we're done.

The 2nd line determines the related element for the event. For a mouseout that's where the mouse was moved and for a mouseover it's where the mouse came from.

The 3rd line works its way up the DOM tree until it gets to the top of the tree or finds the event handler.

When we reach the last line reltg will either equal the event handler meaning the event was fired by a child element or be null meaning the top of the DOM tree was reached without finding the handler.

Have a look at the source for the examples above too see how the function can be used.