Detect if Mouse is over an object inside canvas
Asked Answered
C

1

7

I have created a line inside a canvas element. I am looking for the easiest way to detect if the position of the mouse is inside the line, which is inside the canvas.

I have used this function to see the position of the mouse inside the canvas, but I am very confused on how I should proceed.

function getMousePos(c, evt) {
            var rect = c.getBoundingClientRect();
            return {
                x: evt.clientX - rect.left,
                y: evt.clientY - rect.top
            };
        }

I have also looked at this topic Fabricjs detect mouse over object path , but it detects if the mouse is inside the canvas, not inside the object.

The line that I create is a part of smaller lines, connected to each other.

 for (var i = 0; i < 140 ; i++) {

                ctx.beginPath();

                ctx.moveTo(x[i],y[i]);
                ctx.quadraticCurveTo(x[i],50,x[i+1],y[i+1]);
                ctx.lineWidth = 40;

                ctx.strokeStyle = 'white';
                ctx.lineCap = 'round';
                ctx.stroke();

            }

where x[i] and y[i] are the arrays with the coordinates that I want.

I hope my question is clear, although I am not very familiar with javascript.

Thanks Dimitra

Chamfer answered 4/6, 2014 at 17:37 Comment(1)
D
17

A Demo: http://jsfiddle.net/m1erickson/Cw4ZN/

enter image description hereenter image description here

You need these concepts to check if the mouse is inside a line:

  • Define the starting & ending points of a line

  • Listen for mouse events

  • On mousemove, check if the mouse is within a specified distance of the line

Here's annotated example code for you to learn from.

$(function() {

  // canvas related variables
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");
  var $canvas = $("#canvas");
  var canvasOffset = $canvas.offset();
  var offsetX = canvasOffset.left;
  var offsetY = canvasOffset.top;

  // dom element to indicate if mouse is inside/outside line
  var $hit = $("#hit");

  // determine how close the mouse must be to the line
  // for the mouse to be inside the line
  var tolerance = 5;

  // define the starting & ending points of the line
  var line = {
    x0: 50,
    y0: 50,
    x1: 100,
    y1: 100
  };

  // set the fillstyle of the canvas
  ctx.fillStyle = "red";

  // draw the line for the first time
  draw(line);

  // function to draw the line
  // and optionally draw a dot when the mouse is inside
  function draw(line, mouseX, mouseY, lineX, lineY) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.moveTo(line.x0, line.y0);
    ctx.lineTo(line.x1, line.y1);
    ctx.stroke();
    if (mouseX && lineX) {
      ctx.beginPath();
      ctx.arc(lineX, lineY, tolerance, 0, Math.PI * 2);
      ctx.closePath();
      ctx.fill();
    }
  }

  // calculate the point on the line that's 
  // nearest to the mouse position
  function linepointNearestMouse(line, x, y) {
    //
    lerp = function(a, b, x) {
      return (a + x * (b - a));
    };
    var dx = line.x1 - line.x0;
    var dy = line.y1 - line.y0;
    var t = ((x - line.x0) * dx + (y - line.y0) * dy) / (dx * dx + dy * dy);
    var lineX = lerp(line.x0, line.x1, t);
    var lineY = lerp(line.y0, line.y1, t);
    return ({
      x: lineX,
      y: lineY
    });
  };

  // handle mousemove events
  // calculate how close the mouse is to the line
  // if that distance is less than tolerance then
  // display a dot on the line
  function handleMousemove(e) {
    e.preventDefault();
    e.stopPropagation();
    mouseX = parseInt(e.clientX - offsetX);
    mouseY = parseInt(e.clientY - offsetY);
    if (mouseX < line.x0 || mouseX > line.x1) {
      $hit.text("Outside");
      draw(line);
      return;
    }
    var linepoint = linepointNearestMouse(line, mouseX, mouseY);
    var dx = mouseX - linepoint.x;
    var dy = mouseY - linepoint.y;
    var distance = Math.abs(Math.sqrt(dx * dx + dy * dy));
    if (distance < tolerance) {
      $hit.text("Inside the line");
      draw(line, mouseX, mouseY, linepoint.x, linepoint.y);
    } else {
      $hit.text("Outside");
      draw(line);
    }
  }

  // tell the browser to call handleMousedown
  // whenever the mouse moves
  $("#canvas").mousemove(function(e) {
    handleMousemove(e);
  });

}); // end $(function(){});
body {
  background-color: ivory;
}

canvas {
  border: 1px solid red;
}
<!doctype html>
<html>

<head>
  <link rel="stylesheet" type="text/css" media="all" href="css/reset.css" />
  <!-- reset css -->
  <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

</head>

<body>
  <h2 id="hit">Move mouse near line</h2>
  <canvas id="canvas" width=300 height=300></canvas>
</body>

</html>

About hit-testing Paths:

If you create Paths using path commands you can use context.isPointInPath(mouseX,mouseY) to check if the mouse is inside a path. context.isPointInPath does not work well with lines however because lines theoretically have zero width to "hit".

Dynamometer answered 4/6, 2014 at 18:15 Comment(9)
Thanks a lot for your answer. I was wondering if I can use this with a curved line (which is a part of more lines). I will edit my question, in order to make clear how I create this line so far.Chamfer
If your path is closed then you can use isPointInPath to see if the mouse is inside your closed path. There is a new isPointInStroke which is supported in modern browsers. isPointInStroke will hit-test a line path. Otherwise you will have to "do it manually" as in my answer. Another way is to get all the pixel data about the canvas and then check if the pixel under the mouse is opaque (is on the line) or is transparent (is not on the line).Dynamometer
No, my path is not closed. So, I guess I cannot use the 'isPointInStroke'. I found this link neimke.blogspot.nl/2011/03/… , maybe there is an easier solution for what I am looking for.Chamfer
If you can tolerate some false positives you can still use isPointInPath. Example: jsfiddle.net/m1erickson/2hYHa The isPointInPath hit-test will "invisibly close" the path for you and hit test the closed path.Dynamometer
...And here is an example that reads the pixels from the canvas. You can test this pixel array to see if the mouse is over an opaque pixel (over a part of the line): jsfiddle.net/m1erickson/K2PDZDynamometer
...And if your shapes can be reduced to rectangles or circles (or combinations of rectangles and circles) then you can easily use Math to hit test whether the mouse is inside a rectangular or circular shape.Dynamometer
Thank you a lot for your effort. The pixel array really works for me, although my program crashes (due to a huge width of canvas that I have, I guess, but I will try to solve it). I accepted your answer, since I didn't make clear from the beginning the kind of line that I had.Chamfer
If most of your huge-width canvas is transparent (probably so) then you can make the pixel array work with less resources by creating an array with only the alpha pixel information (you don't need the red,green,blue values just to check if a pixel is transparent). And instead of loading a whole canvas worth of pixel data at once you could do getImageData in sections: (1) .getImageData for the left half of the canvas (2) load the data into the alpha array (3) .getImageData for the right half of the canvas (4) load the data into the alpha array. Cheers!Dynamometer
Indeed my canvas is transparent, but I cannot get it worked for the alpha. I am not sure why. I tried var d=ctx.getImageData(0,0,canvas.width,canvas.height).data[3], but I think it is not right. Any suggestions? :DChamfer

© 2022 - 2024 — McMap. All rights reserved.