Micha Schwab
Micha Schwab

Reputation: 798

Detecting Mouse Events on Multiple Overlapping SVG Elements

I'm trying to detect mousemove events on partially overlapping SVG elements, as in this image

enter image description here

fiddle

<svg>
    <rect id="red"    x=10 y=10 width=60 height=60 style="fill:#ff0000" />
    <rect id="orange" x=80 y=10 width=60 height=60 style="fill:#ffcc00" />
    <rect id="blue"   x=50 y=30 width=60 height=60 style="fill:#0000ff; fill-opacity: 0.8" />
</svg>

$('rect').on('mousemove', function()
{
    log(this.id);
});

Now, when hovering the mouse over the blue/red intersection I'd like to detect mouse events on both those elements, and the same for the blue/orange combo. As you can see in the logs, in those cases the event is currently only fired for the blue box as it is on top.

This has to do with pointer-events, as I can get the red and orange elements to fire the event while hovering the blue element by setting the blue element's pointer-events to none. But then I don't get the events for the blue box, so that is not a viable option either.

I will use whichever library solves this problem. I looked at event bubbling like in this d3 example, but that only works for elements that are nested in the DOM. I have lots of independent elements that may overlap with lots of other elements and can therefore not structure my DOM that way.

I'm guessing the last resort is to find the elements that are at the current mouse position, and manually firing the events. Therefore, I looked at document.elementFromPoint(), but that would only yield 1 element (and may not work in SVG?). I found the jQuerypp function within, that finds the elements at a given position, see here. That example looks great, except it's DIVs and not inside SVG. When replacing divs with svg rectangle elements, the fiddle seems to break.

What do I do?!

Upvotes: 11

Views: 4869

Answers (2)

Vladimir Mujakovic
Vladimir Mujakovic

Reputation: 680

For anyone still looking, elementsFromPoint() returns a node list of all the elements under your mouse cursor.

NOTE: there is also a elementFromPoint() method.

This is particularly useful when you need to detect multiple overlapping SVG path elements on mouseover.

A simple example:

Get the nodeList from your mouse event.

const _overlapped = document.elementsFromPoint(e.pageX, e.pageY)

Filter the list based on some criterion:

// Some list of element id's you're interested in
const _lines = ['elId1', 'elId2', 'elId3'] 

// Check to see if any element id matches an id in _lines   
const _included = _overlapped.filter(el => _lines.includes(el.id))

// Perform an action on each member in the list
_included.forEach(...) 

Upvotes: 4

Micha Schwab
Micha Schwab

Reputation: 798

The great comments here gave me the answer: It's possible to propagate the event to underlying elements manually by finding them using getIntersectionList() at the cursor positon.

$('svg').on('mousemove', function(evt)
{
    var root = $('svg')[0];
    var rpos = root.createSVGRect();
    rpos.x = evt.clientX;
    rpos.y = evt.clientY;
    rpos.width = rpos.height = 1;
    var list = root.getIntersectionList(rpos, null);

    for(var i = 0; i < list.length; i++)
    {
        if(list[i] != evt.target)
        {
            $(list[i]).mousemove();
        }
    }
});

Working example: http://jsfiddle.net/michaschwab/w0wufbtn/6/

If the other listeners need the original event object, check out http://jsfiddle.net/michaschwab/w0wufbtn/13/.

Thanks a lot!!

Upvotes: 7

Related Questions