Search code examples
javascriptsvgonclickpointer-events

How to find nested and transformed SVG element (e.g. circle / path / ...) based on click position


I am a beginner in web development & svg related topics.

I think this should be easy for you svg experts, but I am struggling with this for a while now. All the resources I found on this topic don't seem to work for my fairly simple goal.

My application should run in Chrome browser.

The DOM structure like this:

<div>
    <svg>
        <g>
            <circle></circle>
        </g>
    <svg>
</div>

There could be way more svg tags or nested svg elements inside though, but this is the general setup.

The goal:

I want to click somewhere and find the svg child element (e.g. the circle) which is at the position of my click. The basic function I was thinking of was:

document.elementFromPoint(x, y)

The problems:

1) All the elements can have transformations, like translate, scale and rotation applied to them, which complicates things.

2) I only want to be able to click elements, which are children of an svg node.

Some of my tries:

https://jsfiddle.net/a6uLn9cn/3/

I tried to put "pointer-events:none" on the DIV and / or the SVG node. This way, I am able to avoid that these are "clickable". There are two problems here though: I need pointer-events on the SVG node, as it needs to be zoomed & panned, and also the click events on e.g. the circle "bubbles" up to the DIV afterwards somehow.

Another way to make sure that only children of an svg node are "clickable" can be to check the element.ownerSVGDocument for the found element. If it is null or undefined, I know the found element is not a svg child.

To tackle the problem of actually clicking on an element which has been transformed (either by itself or through its parent), I tried to use element.getBoundingClientRect() which gives me the surrounding rectangle of an element. With this, I made a function to determine if the click was inside of an elements surrounding rectangle:

e is the click event, elem is the found element recieved by document.elementFromPoint()

function clickIsInside(e, elem){
    var clickX = e.clientX;
    var clickY = e.clientY;

    var boxLeft = elem.getBoundingClientRect().left;
    var boxRight = elem.getBoundingClientRect().right;
    var boxTop = elem.getBoundingClientRect().top;
    var boxBottom = elem.getBoundingClientRect().bottom;

    if(clickX >= boxLeft && clickX <= boxRight && clickY >= boxTop && clickY <= boxBottom){
        return true;
    } else {
        return false;
    }
}

But in my jsfiddle, you will not be able to "click" the path for example on the far right side of its boundingClientRect, which makes no sense to me if you imagine where the boundingClientRect is.

Also if i add the path to the DOM after i added the circles, I will not be able to click the circles anymore, but only the path. I totally understand why, but what if i need to click both?

I am not sure if I was able to clearify my troubles here, but I will be very thankful for any input you guys can possibly give me. Thank you very much.


Solution

  • I found out that most of the things i did in my question are just complicating things. The solution was just to check whether the found element has any ownerSVGDElement, and to not dispatch a click event if it does not. Applied transformations on the elements do not seem to be any problem, since document.elementFromPoint() is robust enough to find transformed elements. This question seems useless now, so I answered & closed it just for the rare condition that anybody will ever encounter or try such brainless things as i did.

    Edit: Seems like i can only accept this as answer in 2 days.