21 October 2007

Horrible onmouseout flicker in IE

For some kinds of application, it's useful to highlight a row when the mouse goes over it. Using CSS this is easy to implement:

tr:hover {
background-color: #123;
}

Unfortunately, we're stuck in the IE-age, and this simple, elegant solution doesn't work with the world's most popular browser. There appears to be a straightforward, if annoying, workaround: a little bit of onmouseover/onmouseout magic to change the row's CSS class.

A reasonable person might expect that, once the mouse enters the row, we would consider that it is still over the row until such time that it actually leaves the row. In other words, if I enter my house, and then enter my kitchen, I am still in my house, despite also being in my kitchen. Notwithstanding everything I learned at school about physics, it appears that I can be in more than one place at one moment, and, indeed, my mouse can too.

The authors of IE didn't think so

When the mouse enters the row, and then enters, say, an image in a cell in the row, an onmouseout event is fired on the row.

This doesn't break the straightforward if annoying workaround - but it flickers so much you might worry about having a seizure.

So one day we came up with this workaround to make the workaround work:

function really_over(src) {
  if (!window.event) return true;
  var event = window.event;
  var from = event.fromElement;
  var to = event.toElement;
  return ( to == src || src.contains(to) ) && !src.contains(from) && src != from;
}

function really_out(src) {
  if (!window.event) return true;
  var event = window.event;
  var from = event.fromElement;
  var to = event.toElement;
  return (src == from || src.contains(from)) && !src.contains(to) && src != to;
}

It simply returns true if an onmouseout event really represents a mouse out event. The effect of if (!window.event) return true; at the top is to return true if the browser is not IE, in which case the reasonable-person interpretation of "mouse out" applies.

Use as follows:

<tr onmouseout="if(really_out(this)) {unhoverRow(this);}">

where unhoverRow() is your function for changing the element's CSS class.

Of course, if you didn't want to do any of this, you might prefer to use Prototype's observe method, which is a lot nicer and appears to deal with the flicker thing internally ...

9 comments:

  1. Great work! I tried a thousend times to make a workaround for this horrible onmouseout bug and this is the solution.

    ReplyDelete
  2. Thanks. As a newb, it was extremely helpful to have this code and explanation.

    ReplyDelete
  3. Thank you. It took me a while to understand how it worked but I found out.

    For other newbs like me follow this guideline.

    Replace the first "this" with the containers ID which mouseout is being called on.

    Replace unhoverRow(this); with the function you wish to call after mouseout.


    Ciao

    ReplyDelete
  4. Thanks a lot!

    ReplyDelete
  5. Thanks a lot for the code, however I'm having trouble getting it to work in Firefox. Has anyone else had this problem?

    ReplyDelete
  6. Great!!! thanks for the help man, I was tired searching on google for a suitable solution.

    ReplyDelete
  7. Thanks, good work =D

    ReplyDelete
  8. To the guy asking about Firefox, it worked for me by just removing this line:

    if (!window.event) return true;

    I think the author of the above (which is great, by the way, thanks!) was incorrect when he suggested that it's only IE that suffers from the flicker bug. It's happening to me in Firefox (3.6.9) too.

    ReplyDelete
  9. This is the perfect solution for IE onmouseout flickering issue. Thanks alot buddy.. :) ..Brilliant!!!....

    ReplyDelete