Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canvas isPointInPath() with multiple paths

I have a canvas "object" named Box and I need detect when mouse is over it.

I have a draw() method for this object which I use isPointInPath() method, but only change when the cursor is on the last path. Any suggestions?

Box.prototype.draw = function() {

    this.ctx.beginPath();
    this.ctx.moveTo(this.matrix.p1.x, this.matrix.p1.y);
    this.ctx.lineTo(this.matrix.p2.x, this.matrix.p2.y);
    this.ctx.lineTo(this.matrix.p3.x, this.matrix.p3.y);
    this.ctx.lineTo(this.matrix.p4.x, this.matrix.p4.y);
    this.ctx.lineTo(this.matrix.p1.x, this.matrix.p1.y);
    this.ctx.closePath();

    this.ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
    this.ctx.fill();

    if (this.ctx.isPointInPath(mouse.x, mouse.y)) {
        this.canvas.style.cursor = 'pointer';
        this.ctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
        this.ctx.fill();
        return;
    }

    this.canvas.style.cursor = 'default';

};
like image 259
eledgaar Avatar asked Jun 14 '26 19:06

eledgaar


2 Answers

You can store each path in an array or object to access them later.

In general you need to create a Path2D. (check browser support before using).

Here's a really simple grid of hexagons to demonstrate this: http://codepen.io/pixelass/pen/37445407893ef783e414ce136af5633a

const C = document.createElement('canvas');
const $ = C.getContext('2d');

const rows = 5;
const cols = 5;
var side = 50;
C.width = cols * side * Math.sqrt(3);
C.height = rows * 1.5 * side;

const paths = [];

var dy = -1;
for (let i = 0; i < (rows + 1) * (cols + 1); i++) {
  let dx = i % (cols + 1);
  dy += dx ? 0 : 1;
  dx += dy % 2 ? -.5 : 0;
  let cx = dx * (side * Math.sqrt(3)) + side / 2 * Math.sqrt(3);
  let cy = (dy - .5) * (side * 1.5) + side;
  let path = new Path2D();
  for (let j = 0; j < 6; j++) {
    let x = Math.cos(Math.PI / 3 * j + Math.PI / 6) * side + cx;
    let y = Math.sin(Math.PI / 3 * j + Math.PI / 6) * side + cy;
    if (j) {
      path.lineTo(x, y);
    } else {
      path.moveTo(x, y);
    }
  }
  path.closePath();
  $.fillStyle = `hsl(${10*i},50%,50%)`;
  $.fill(path);
  paths.push(path);
}

C.addEventListener('mousemove', e => {
  let bound = C.getBoundingClientRect();
  let x = e.pageX - bound.left;
  let y = e.pageY - bound.top;
  paths.forEach((path, index) => {
    if ($.isPointInPath(path, x, y)) {
      console.log(index);
    }
  });
});

document.body.appendChild(C);

context.isPointInPath only tests the very last path defined (from the last context.beginPath).

So you must individually test each of your shape paths:

  • "Redefine" the first shape shape. Redefine means reissuing the first shape's path commands--but you don't need to actually stroke() or fill() the first shape.

  • Test if the mouse is inside the first shape using isPointInPath.

  • Continue testing the second, third, ... last shape.

BTW, if all of your shapes are rectangles you can use math to test if the mouse is inside any rectangle:

var isInside=(
    mouseX>=RectX && 
    mouseX<=RectX+RectWidth &&
    mouseY>=RectY &&
    mouseY<=RectY+RectHeight
);
like image 23
markE Avatar answered Jun 17 '26 07:06

markE