13

What would be the best way to implement a mouseenter/mouseleave like event in JavaScript without jQuery? What's the best strategy for cross browser use? I'm thinking some kind of checking on the event.relatedTarget/event.toElement property in the mouseover/mouseout event handlers?

Like to hear your thoughts.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Mansiemans
  • 896
  • 2
  • 12
  • 23

4 Answers4

14

(Totally changed my terrible answer. Let's try again.)

Let's assume you have the following base, cross-browser event methods:

var addEvent = window.addEventListener ? function (elem, type, method) {
        elem.addEventListener(type, method, false);
    } : function (elem, type, method) {
        elem.attachEvent('on' + type, method);
    };

var removeEvent = window.removeEventListener ? function (elem, type, method) {
        elem.removeEventListener(type, method, false);
    } : function (elem, type, method) {
        elem.detachEvent('on' + type, method);
    };

(Pretty simple, I know.)

Whenever you implement mouseenter/mouseleave, you just attach events to the normal mouseover/mouseout events, but then check for two important particulars:

  1. The event's target is the right element (or a child of the right element)
  2. The event's relatedTarget is not a child of the target

So we also need a function that checks whether one element is a child of another:

function contains(container, maybe) {
    return container.contains ? container.contains(maybe) :
        !!(container.compareDocumentPosition(maybe) & 16);
}

The last "gotcha" is how we would remove the event listener. The quickest way to implement it is by just returning the new function that we're adding.

So we end up with something like this:

function mouseEnterLeave(elem, type, method) {
    var mouseEnter = type === 'mouseenter',
        ie = mouseEnter ? 'fromElement' : 'toElement',
        method2 = function (e) {
            e = e || window.event;
            var target = e.target || e.srcElement,
                related = e.relatedTarget || e[ie];
            if ((elem === target || contains(elem, target)) &&
                !contains(elem, related)) {
                    method();
            }
        };
    type = mouseEnter ? 'mouseover' : 'mouseout';
    addEvent(elem, type, method2);
    return method2;
}

Adding a mouseenter event would look like this:

var div = document.getElementById('someID'),
    listener = function () {
        alert('do whatever');
    };

mouseEnterLeave(div, 'mouseenter', listener);

In order to remove the event, you'd have to do something like this:

var newListener = mouseEnterLeave(div, 'mouseenter', listener);

// removing...
removeEvent(div, 'mouseover', newListener);

It's hardly ideal, but all that's left is just implementation details. The important part was the if clause: mouseenter/mouseleave is just mouseover/mouseout, but checking if you're targeting the right element, and if the related target is a child of the target.

sdleihssirhc
  • 42,000
  • 6
  • 53
  • 67
  • This script, unfortunately, was firing around 40-50 times on a single mouseenter. If you do a simple `mouseEnterLeave(ele, 'mouseenter', function(){ console.log('TEST'); });` you'll get "test" in your console around 40 times. – Oscar Godson Dec 18 '11 at 20:38
  • 1
    @OscarGodson Can you be more specific? I tried a very simple example in a bunch of browsers and couldn't reproduce your issue. (Though I did find a stupid IE bug that I've incorporated into the answer.) – sdleihssirhc Dec 19 '11 at 18:08
  • "a function that checks whether one element is a **child** of another". You mean **descendant**, right? – bruha Nov 04 '14 at 14:49
2

The best way, imho, is to craft your own event system.

Dean Edwards wrote one some years ago that I've taken cues from in the past. His solution does work out of the box however.

http://dean.edwards.name/weblog/2005/10/add-event/

John Green
  • 13,241
  • 3
  • 29
  • 51
1

John Resig submitted his entry to a contest, in which his was judged the best (Note: Dean Edwards was one of the jury). So, I would say, check this one out too.

Also its doesn't hurt to go thru jQuery, DOJO source once in a while, to actually see the best practices they r using to make it work cross-browser.

a6hi5h3k
  • 671
  • 3
  • 7
  • Actually, the solution I posted above was in response to the Resig submission. He decided that he didn't like certain aspects of how it worked, so he recoded it. Hence why the Edwards post is one month later. : ) I like the Edwards solution a bit better (for many of the reasons he specifies).... although I'm definitely a fan of Resig and his work. – John Green May 25 '11 at 21:54
  • thanks for mentioning it. the guid concept is sth that even jQuery adopted. – a6hi5h3k May 31 '11 at 02:50
0

another option is to distinguish true mouseout events from fake (child-generated) events using hit-testing. Like so:

elt['onmouseout']=function(evt){
  if (!mouse_inside_bounding_box(evt,elt)) console.debug('synthetic mouseleave');
}

I've used something like this on chrome and, caveat emptor, it seemed to do the trick. Once you have a reliable mouseleave event mouseenter is trivial.

amwinter
  • 3,121
  • 2
  • 27
  • 25