document.elementFromPoint returning different elements for same input

I am using document.elementFromPoint to figure out what svg shape is at a certain point. I was experimenting with elementFromPoint on this image when I noticed that I was getting different outputs for the same input.Image of chrome devtools console where document.elementFromPoint is called with the arguments 0.25 and 50 in 2 different places, returning different svg elements

Here is exactly what I did for reproducibility.

  1. I cut and pasted the svg tag from the linked svg and inserted it into the body of an html file. The html file was set up using the single ! emmet shortcut in vscode. Nothing else was in the body. The only css was body{margin:0;}.
  2. In chrome I went around calling elementFromPoint. Mostly at .25,50 but sometimes just a little to the right or a little to the left. Sometimes calling with the same arguments over and over again to see if it would change.
  3. There was no scrolling done during this time. There wasn't even an option as there was no scroll bar present.

My question is why does this happen? Is there a pattern to it? Is there a way to prevent it? Thanks.

1 answer

  • answered 2022-01-25 16:29 ccprog

    While I can't tell you why this happens, or how to prevent it, the "pattern", or let's better say the reason for the inconsistent behavior can be narrowed down.

    Let's look at the paths that are returned by your calls to elementFromPoint. There are two of them, and if you leave out the ids and classes, both look identical, even taking into consideration their parent elements:

    <g transform="translate(-1.775,-1.575)">
        <path d="M 1.9,551.3 V 1.7 H 1102 V 551.3 H 2.3" />
    </g>
    

    If you rewrite the path such that the transform is resolved, you get a path in (browser) viewport coordinates - for clarity, I have rewritten the V and H commands as L:

    <path d="M 0.125,549.725 L 0.125,0.125 L 1100.125,0.125 L 1100.125,549.725 L 0.525,549.725" />
    

    The SVG contains a stylesheet that describes the filling and stroking of these elements. This is the relevant excerpt:

    .seabase {
      fill: #C6ECFF;
      stroke: none;
    }
    .mapborder {
      fill: none;
      stroke: #0978AB;
    }
    path, circle {
      stroke-width: 0.25;
    }
    

    So what should be returned from document.elementFromPoint(0.25,50)? The topmost element to be found at the given coordinates, provided the pointer-events property allows the element to be the target of a hit-test.

    pointer-events is not explicitely set for the elements, so the default value of visiblePainted applies. This means that an (at least partially) opaque fill or stroke both can be hit.

    The bottom element of the two candidates, <path class="seabase"/>, has a visible fill, but no border. Point (0.25,50) is inside that fill.

    The topmost element of the two candidates, <path class="mapborder"/>, has no fill but a visible border of width 0.25, extending half to the inside and half to the outside of the path outline. At the left side, there is a vertical subpath M 0.125,549.725 L 0.125,0.125. Not going into the details of corners, the stroke could be drawn equivalently as a filled path with a definition

    M 0,549.725 L 0,0.125 L 0.25,0.125 L 0.25,549.725 Z
    

    In other words, the point (0.25,50) is exactly on the outline of the stroke. The spec is quite clear about the consequences:

    The zero-width geometric outline of a shape is included in the area to be painted.

    The point should hit the mapborder element, as the point is part of its visible, painted stroke. If, as you describe, the browser sometimes returns the seabase element, it is in error.

    I could go on and speculate about the reasons for this behavior, but that is moot, I think.

    Can you prevent it? Only if you would be able to consistently avoid the exact outline of all paths, or if they have a stroke, the outline of that stroke. That's hardly practical, since you would have to find out if the browser missed something it should have hit, while you don't know what was missed.

    Finally, there is a second interface that can be used. It is part of the SVG specification, while elementFromPoint is part of the CSSOM spec. Whether that makes a difference in the browser implementation, I cannot say.

    document.querySelector('.seabase').isPointInFill(new DOMPoint(2.025,48.425));
    document.querySelector('.mapborder').isPointInStroke(new DOMPoint(2.025,48.425));
    

    Note that you need to provide an element to test against, that you get no information which element is on top, and that the coordinates are expressed in local userspace (before transform is applied).

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum