Forum Moderators: open
I have an element container
Inside it is some HTML content.
I need to trigger some JS behaviour when the mouse leaves the bounds of the container. Isn't that "onmouseout"?
Problem is, the event is firing when my mouse passes over elements inside the container!
To illustrate, my markup looks something like this:
<div id="container">
<h2>something inside</h2>
<p>some content</p>
</div>
I'm applying event listeners to the container. Note that I'm using a couple of Mootools functions, but that shouldn't be an issue...
$('container').onmouseover = function(e){
$('container').className = 'over';
e.stopPropagation();
return false;
}
$('container').onmouseout = function(e){
alert('you\'re out');
e.stopPropagation();
return false;
}
When I put my mouse over the container, the onmouseover event goes, and it gets a red border. That's Good.
If I leave my mouse in that little "padding" gutter inside the container, all is well. But when I move my mouse over the <h2> element, the onmouseout event fires, and I get the "you're out" alert.
What do I do, and what am I doing wrong?
the solution is here:
[quirksmode.org...]
My annotated and alert-ridden version is below:
$('container').onmouseout = function(e){// if e doesn't exist, get it.
if (!e) var e = window.event;// tg is the element we're leaving
var tg = (window.event) ? e.srcElement : e.target;// show me
// alert('leaving: '+ tg.tagName + ' ' + tg.id);// I have an id on the element, so I can check using ID if tg === container.
// you might have to use some other condition to identify the container
if (tg.id != 'container'){// the element you left is not the container. you probably
// mouseoutted from something inside it. this isn't the mouseout you
// want, so quit now.
return;
}// this is the element we've moved to
var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;// starting with the tg element, we loop up through the DOM.
// we just want to figure out if we're inside the element or outside it
while (reltg != tg && reltg.tagName != 'BODY'){// this is Mootools way of getting the parent element.
reltg = reltg.getParent();if (reltg == tg){
// we found tg as a parent. So this is not really leaving the element
return;
}
}// if you are here, then you have climbed up the DOM to
// the <body> without finding tg// so we know that the one we left is the container, not something inside it
// and we know the one we've entered is not a child of the container.
// so go ahead and do what you came here to do.alert('you\'re out!');
return false;
}
The example at quirksmode performs some unwarranted checks, and fails in certain circumstances. Here's a streamlined, more effective version.
$('container').onmouseout = function(e){
if (!e) var e = window.event;
var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
while (reltg.tagName != 'BODY'){
if (reltg.id == this.id){return;}
reltg = reltg.parentNode;
}
// do your stuff here
alert('you\'re out!');
}
Why is this better?
For one thing, sometimes my elements - even though they have a parent-child relationship in the DOM - are not physically contained in the container (thanks to CSS positioning). Thus sometimes when I mouseout of a child element, I'm not actually mousing-out of the container! Ergo this little piece of the original function isn't appropriate:
if (tg.id != 'container'){
return;
}
I also found that if I move my mouse really fast out of a child element and out of the container, the container doesn't fire its mouseout event. I guess the pointer actually has to rub across a piece of the container element in order for it to fire the event. If the container is stuffed with children with no padding or margins, that isn't going to happen.
So I got rid of the "tg" variable entirely. Paying attention to that property was distracting and misleading, since it doesn't always fire on the element we're trying to watch anyways.
This version, significantly lighter, only checks one property of the event - the target you just moused onto. It doesn't care what you moused from. It loops from the target element up the DOM ancestry. If along its path through the ancestry it finds the container, then we know the user moused onto a child element within the container, the function returns, and nothing happens. If it gets to the BODY and hasn't found the container in its ancestry, then we know the user moused onto an element outside the container (in the DOM, not necessarily in the rendered page), and we should treat it as a true container.onmouseout.
The new version also uses "this" effectively since the event is scoped to the container element.
An interesting side-effect of this change is that we can have child elements positioned outside the bounds of the container (absolutely or relatively), and holding your mouse over them will not trigger the container's mouseout - even though the mouse's actual position may be outside the bounds of the parent container. This will be a useful quirk to employ when building drop-down menus, navigation trees, tabbed option menus... anywhere that a child element may be offset from the parent, and the parent must stay open while the mouse is over a child.
Oh, and I found that mootool's getParent() function wasn't working reliably in IE for certain elements, like LI and P. I'm not sure why. So I reverted it back to use the native element.parentNode property, which works in all the browsers I've tried.
Enjoy!