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 ...