Reputation: 798
I'm trying to detect mousemove events on partially overlapping SVG elements, as in this image
<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
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
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