Как определить, находится ли точка внутри сложной, неправильной векторной формы (SVG)?

Я более-менее новичок во всем этом. Я пытаюсь создать приложение с использованием SVG (я бы предпочел векторные фигуры, которые можно масштабировать без потери качества). Используя веб-технологии и позже перейдем на React-native.

Есть ли какой-либо способ узнать, находится ли точка с координатами x, y и которая также будет определяться свойствами CSS left и top внутри очень сложной, неправильной векторной формы?

Я знаю, что мой вопрос полон ограничений, поэтому на него действительно сложно ответить, поэтому даже указание на правильную документацию было бы полезно.

PS: Я пробовал использовать метод elementFromPoint(), но он возвращает ограничивающий прямоугольник, который DOM помещает вокруг моего SVG. Это полностью противоречит моей цели, я бы хотел убедиться, что точка обнаруживается только в пределах SVG Paths и самой формы.

Свободный перевод вопроса How to detect if a point is inside complex, irregular vector shape (SVG)? от участника @Steve M..


Ответы (2 шт):

Автор решения: Alexandr_TT

Пока вы используете встроенные SVG, elementFromPoint() должен работать нормально:

const shape = document.querySelector('#shape'),
      status = document.querySelector('#inout');

document.body.addEventListener('mousemove', function(e) {
    const x = e.clientX,
          y = e.clientY;
    
    if(document.elementFromPoint(x, y) === shape) {
        status.className = status.textContent = 'inside';
    }
    else {
        status.className = status.textContent = 'outside';
    }
});
h2 { margin: 0; }

.inside { color: lime; }
.outside { color: red; }
<h2>The cursor is: <span id="inout" class="outside">outside</span></h2>

<svg xmlns="http://www.w3.org/2000/svg" width="250" height="200">
    <path id="shape" d="M150,120  q26,75 -80,50  c-80,-25 -53,-125 -26,-125  c53,-25 107,-25 107,50  q107,-50 53,50 z"
          stroke-width="4" fill="yellow" stroke="limegreen" />
</svg>

Свободный перевод ответа от участника @Sphinxxx.

→ Ссылка
Автор решения: Leonid

Хотя ответ не совсем по вопросу (проверка точки на нахождение внутри Path), но для событий мыши, например, можно пользоваться элементами SVG как обычными элементами HTML. Можно вешать слушатели непосредственно на сложную <path>.

const path = document.querySelector('path');
const text = document.querySelector('p');

path.addEventListener('mousemove', () => {
    text.textContent = 'Inside the path';
})

path.addEventListener('mouseout', () => {
    text.textContent = 'Outside the path';
})
<svg viewBox="0 0 300 300" height="100vh">
    <path d="M118 211c-5.88,0.89 -13.66,2 -20,2 -24.3,0 -44,-19.7 -44,-44 0,-24.3 19.7,-44 44,-44 6.92,0 11.95,1.33 18,2 9,1 29,-21 30,-29 0.4,-3.21 0.21,-5.2 -0.01,-5.82 -2.92,-8.14 -6.76,-19.68 -6.76,-28.34 0,-27.61 22.38,-50 50,-50 27.61,0 50,22.39 50,50 0,27.62 -22.39,50 -50,50 -10.27,0 -17.23,-2.84 -25.23,-3.84 -13.24,-1.66 -34,25 -31,32 2.8,6.54 9,16.92 9,27 0,9.96 -4.17,17.36 -8.61,25.12 -7.39,12.88 19.85,34.98 27.41,31.96 3.45,-1.37 7.25,-2.08 11.2,-2.08 17.12,0 31,13.88 31,31 0,17.12 -13.88,31 -31,31 -17.12,0 -31,-13.88 -31,-31 0,-4.74 1.73,-9.5 2.49,-14.02 0.84,-4.92 -10.48,-32.26 -25.49,-29.98z" fill="red"></path>
</svg>
<p style="position:fixed;top:0;right:10px">
Outside the path
</p>

Если не искать легких путей, то можно отрисовать path на скрытом canvas в правильном масштабе. А уже по событию мыши проверять ctx.isPointInPath(). При разворачивании или прокрутке окна перемасштабирования не происходит. Можно добавить window.onresize и .onscroll.

const svg = document.querySelector('svg');
const path_string = svg.querySelector('path').getAttribute('d');
const text = document.querySelector('p');

let rect = svg.getBoundingClientRect();
let view_width = svg.getAttribute('viewBox').split(' ')[2];

const canvas = document.createElement('canvas');
let w = canvas.width = rect.width;
let h = canvas.height = rect.height;
const ctx = canvas.getContext('2d');
ctx.scale(rect.width/view_width, rect.width/view_width);
let path = new Path2D(path_string);
ctx.fill(path);

document.addEventListener('mousemove', detectInPath);

function detectInPath(e){
    if(ctx.isPointInPath(path,e.clientX - rect.x, e.clientY - rect.y, 'evenodd')){
        text.textContent = 'IN path';
    } else {
        text.textContent = 'OUT of path';
    }
}
<svg viewBox="0 0 300 300" height="100vh">
    <path d="M118 211c-5.88,0.89 -13.66,2 -20,2 -24.3,0 -44,-19.7 -44,-44 0,-24.3 19.7,-44 44,-44 6.92,0 11.95,1.33 18,2 9,1 29,-21 30,-29 0.4,-3.21 0.21,-5.2 -0.01,-5.82 -2.92,-8.14 -6.76,-19.68 -6.76,-28.34 0,-27.61 22.38,-50 50,-50 27.61,0 50,22.39 50,50 0,27.62 -22.39,50 -50,50 -10.27,0 -17.23,-2.84 -25.23,-3.84 -13.24,-1.66 -34,25 -31,32 2.8,6.54 9,16.92 9,27 0,9.96 -4.17,17.36 -8.61,25.12 -7.39,12.88 19.85,34.98 27.41,31.96 3.45,-1.37 7.25,-2.08 11.2,-2.08 17.12,0 31,13.88 31,31 0,17.12 -13.88,31 -31,31 -17.12,0 -31,-13.88 -31,-31 0,-4.74 1.73,-9.5 2.49,-14.02 0.84,-4.92 -10.48,-32.26 -25.49,-29.98z" fill="red"></path>
</svg>
<p style="position:fixed;top:0;right:10px">OUT of path</p>

→ Ссылка