Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional mouse-event pass-through between sibling elements that overlap

Is there a standard Javascript technique or library for managing conditional mouse-event pass-through between overlapping HTML elements that are not related?

For example, I have a partially transparent WebGL canvas (managed by Three.js) in front of a bunch of HTML elements (managed by the Thee.js CSS3 renderer, but that shouldn't be relevant). Those HTML elements have registered mouseover and mouseout events. I would like 3D objects floating in front of those elements to block mouse-events.

I already know how to use a ray-caster to determine whether the mouse is over a 3D object. What I don't know is how to allow the mouse-event to 'pass through' the canvas to the underlying HTML elements when a 3D object is not inbetween.

enter image description here

I've read about solutions where you traverse the DOM tree until you find the element that's underneath the mouse. But that seems overly complicated and slow. What I'd like to do, if possible, is pretend that the canvas is not there for a moment so that the event can pass through naturally.

In the interest of not reinventing the wheel, it would be great if there was already a library for this.

like image 513
mhelvens Avatar asked Jun 06 '14 13:06

mhelvens


2 Answers

Since you don't want to use the pointer-events as David Thomas suggested, then the mouse will always target the elements with the highest z-index or, having the same z-index, the last sibling (when relatively stacked on top of others).

Having said this, the only way I can think is to:

  1. Hide the canvas
  2. Read element underneath
  3. Immediately show back the canvas

This is so fast that produces no noticeable flickering.

If there was an element underneath the mouse, then trigger that element mouse event.

Demo here

$("canvas").mousemove(function (event) {
    if (document.elementFromPoint) {
        var $canvas = $(this);

        // hide canvas visibility
        // don't do display:none as we want to maintain canvas layout
        $canvas.css('visibility', 'hidden');

        // get the element underneath, if any
        var $under = $(document.elementFromPoint(event.clientX, event.clientY));

        // show again the canvas
        $canvas.css('visibility', 'visible');
        if ($under.hasClass('underneath')) { 
           $under.triggerHandler('mouseover');
        } else {
            $("#result").text("mouse is over the canvas");
        }
    }
})
like image 160
Jose Rui Santos Avatar answered Nov 02 '22 17:11

Jose Rui Santos


I found a pretty straightforward solution for you. And since you provided a jsfiddle example in one of your comments I figured I just tweak that code a little and let you see it.

It is very simple what I am doing. I check if the mouse is overlapping the div and the function "changeTheDivBox" is called when your Three.js raycaster does not hit any boxes.

var divBox = document.getElementById("background");
divBox.addEventListener("mouseover", mouseOver, false);
divBox.addEventListener('mouseout', mouseOut, false);
var mouseOverBox = false; // Are you currently hovering over the "background" div?
function mouseOver () {
    mouseOverBox = true;
}
function mouseOut () {
    mouseOverBox = false;
}
function changeTheDivBox () { // This function is called when your raycaster does not hit any boxes
    if ( mouseOverBox ) {
        divBox.style.backgroundColor = 'blue';
    }
    else {
        divBox.style.backgroundColor = 'green';
    }
}

Basically:

Is the mouse overlapping the <div> ?
    Yes.
    Is the mouse (raycaster) hitting any boxes?
    No.
        Then Change the <div>'s color!

One more thing I did was use the css property pointer-events. This lets you click on divs "further back" by making any chosen elements not register with the mouse events. I addedpointer-events: none; to the body to disable all mouse events and I then added pointer-events: auto; to the div element to re-enable them on it. So the mouse now only detects events on that div. Pretty neat.

I left the rest of the code as it is. I personally would prefer to to use a loop to constantly check if the overlap/raycast is still valid rather than rely on mouseIn / mouseOutSo but it is your example, so play around with it :)

UPDATED JSFIDDLE

Just as a personal preference. I would suggest that you avoid putting functions in the HTML. They don't always work as expected. (The this object keeps referring to the window) And they can make your code more confusing. I personally prefer to keep my JavaScript inside the tags. And eventListeners are more powerful than inline function calls anyways :)

like image 31
Schoening Avatar answered Nov 02 '22 15:11

Schoening