Running an action when mousing over (and leaving) an element (or its child elements)
Imagine I have the following elements:
+------------------+
| |
| +----------------+
| | |
| | |
| +----------------+
| |
+------------------+
The inner element has positioning that mean it can visually sit outside of its parent, and I put an event listener on the parent like such:
parent.addEventListener('mouseover', function(e) {
// log mouse is over me
}, false);
If I mouse directly over the parent from the bottom, I get a direct event, but because of event routing if I mouse over from the side (hitting the child first), I'll also receive the event from the child as it bubbles up.
If I keep going left until I'm out of the child element and in the parent space, I'll get a direct event, if I go right again, I'll get another bubbled event from the child element.
This is by design and the design is absolutely fine, however- if I want to do a SINGLE action when the mouse overs me (including my child items), I need to do some extra work to block events I'm not interested in.. for example, this would work:
var isOver = false;
parent.addEventListener('mouseover', function(e) {
if (isOver) return;
isOver = true;
// log mouse is over me
}, false);
parent.add开发者_如何转开发EventListener('mouseout', function(e) {
if (e.relatedTarget == this || e.relatedTarget.isDescendantOf(this)) return;
isOver = false;
// log mouse is leaving me
}, false);
Essentially the code has a state variable to determine if the mouse has 'overed' the parent (either via its child or not), and if the event fires again whilst 'overed' those events are ignored.. we also capture the mouse out event and ask it.. is the relatedTarget (the target you are about to enter) ME? (the parent), or an eventual child of ME? if so, then don't do anything.. otherwise set the 'overed' state to false.
So that code actually works, but imagine I cannot use the relatedTarget property to determine the next target, how would you go about doing this behavior?
I hope that makes sense, I have the feeling theres not enough context in the events to do it without relatedTarget but felt there might well be an angle regarding changing behavior on the eventPhase property (to determine if the event is direct or bubbled).
Also I'm not looking for answers saying 'this won't work in IE, yadda yadda'.. I'm working against w3c event spec browsers only right now.
Edit, just to clarify, I want the events to happen as if I have a single element that looked like this (if the element was actually as above example):
+------------------+
| |
| +--+
| |
| |
| +--+
| |
+------------------+
I think what you are after are IE's "mouseenter"
event (fires when the user moves the mouse pointer inside the boundaries of the object) and "mouseleave"
event (fires when the user moves the mouse pointer outside the boundaries of the object):
Unlike the
onmouseover
event, theonmouseenter
event does not bubble. In other words, theonmouseenter
event does not fire when the user moves the mouse pointer over elements contained by the object, whereasonmouseover
does fire.
Ditto for onmouseleave
.
Here's an example: http://jsbin.com/uputi (add /edit
to the url to edit the source code):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Sandbox</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<style type="text/css" media="screen">
#outer { width:200px; height:200px; border:1px solid #333; }
#inner { float:right; width:125px; height:80px; margin-right:-80px; border:1px solid #333; }
</style>
<script>
window.onload = function() {
function mouseenter() {
document.getElementById('dbg').innerHTML = 'mouse entered #outer boundary';
}
function mouseleave() {
document.getElementById('dbg').innerHTML = 'mouse left #outer boundary';
}
var outer = document.getElementById('outer');
if ('onmouseenter' in document.body) {
// browser supports mouseenter/mouseleave natively (i.e., IE)
outer.onmouseenter = mouseenter;
outer.onmouseleave = mouseleave;
}
else {
// simulate mouseenter/mouseleave in other browsers
function wrap(fn) {
return function(event) {
var parent = event.relatedTarget;
// Check if we're still within the same parent (traverse up parentNodes)
while (parent && parent != this) {
parent = parent.parentNode
}
if(parent != this) {
// call the *real* mouseover/mouseout handler
fn.call(this, event)
}
}
}
outer.onmouseover = wrap(mouseenter);
outer.onmouseout = wrap(mouseleave);
}
}
</script>
</head>
<body>
<div id="outer">
<div><code>#outer</code></div>
<div> </div>
<div> </div>
<div id="inner"><code>#inner</code></div>
</div>
<div id="dbg"></div>
</body>
</html>
It uses relatedTarget
as you do (which works fine in standards browsers FYI), but without the state flag. See this article for more details of the emulation shown here.
You can see only one mouseenter
event is fired when entering the #outer
element, as well as the #inner
element. This is the "boundary" effect you are going after (I think).
If you have the option of including jQuery in your application, jQuery brings these fantastic events to other browsers. (note jQuery uses the technique outlined her internally, with some minor modifications).
Prototype 1.6.1 also has these events simulated.
I think you simply need to stopPropagation
精彩评论