Search code examples
javascriptjquerysvgsvg-path

how to know if any x or y point is on svg path


i have a svg path and i want to know if my mouse is on svg path or not, if it is i want to change the cursor to mouse to pointer.

This could have been easily done by adding mouse hover property on path and also with Recognize point(x,y) is inside svg path or outside with this solution.

but there is a twist, I have another transparent layer over it, because of which I cannot have those two solutions.

right now I am making top layer display none and it works fine. but because of this my mouse pointer and the action I do such as moving a certain element on mouse move is slow,

hence i want to find out if there is any other better way without making display equal to none.

please find the fiddle example, I want to change the cursor to pointer when its on mypath element and also want myline should be moving as i move mouse over the layer, i can do display to none on layer for time being, but i noticed on firefox, line movement is not that smooth,

https://jsfiddle.net/shyam_bhiogade/9a7zuou2/6/

<svg width="400" height="400">
  <g>
    <path id="mypath" d="M10 200 L200 90 L200 200" fill="transparent" stroke="black" stroke-width="5" />
    <rect class="interactiveArea" width="500" height="500" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0);opacity:0.2" />
    <line id="myline" x1="20" y1="0" x2="20" y2="400" stroke-width="2" stroke="black" />
  </g>
</svg>

Solution

  • I have used the solution given at https://bl.ocks.org/mbostock/8027637 , it returns the distance of x and y point from the path, if the distance is less than 1px or width of the stroke, I consider that x and y point is on the path.

       function closestPoint(pathNode, point) {
      var pathLength = pathNode.getTotalLength(),
          precision = 8,
          best,
          bestLength,
          bestDistance = Infinity;
    
      // linear scan for coarse approximation
      for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
        if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
          best = scan, bestLength = scanLength, bestDistance = scanDistance;
        }
      }
    
      // binary search for precise estimate
      precision /= 2;
      while (precision > 0.5) {
        var before,
            after,
            beforeLength,
            afterLength,
            beforeDistance,
            afterDistance;
        if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
          best = before, bestLength = beforeLength, bestDistance = beforeDistance;
        } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
          best = after, bestLength = afterLength, bestDistance = afterDistance;
        } else {
          precision /= 2;
        }
      }
    
      best = [best.x, best.y];
      best.distance = Math.sqrt(bestDistance);
      return best;
    
      function distance2(p) {
        var dx = p.x - point[0],
            dy = p.y - point[1];
        return dx * dx + dy * dy;
      }
    }