Events attached to object inside canvas

2019-02-15 02:43发布

问题:

I have simply canvas code which draw rect on the canvas

var x=document.getElementById("canvas");
var ctx=x.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();

is it possible to add eventListener on said rect? For example , if i click on rect , it will turn red.

回答1:

Regions

Depending on how well you want to support various and older browsers, there is addHitRegion() that you can use by enabling it through flags in Firefox and Chrome (at the moment of this being written):

Firefox: about:config -> search "hitregions" and set to true
Chrome: chrome://flags/ -> Enable experimental canvas features

This is the only technique that integrates directly with the event system. I would not recommend it for production quite yet though, and AFAIK there is not a polyfill for it either - but to show how easy it is to use:

var x=document.getElementById("canvas");
var ctx=x.getContext("2d");
ctx.rect(20,20,150,100);
ctx.addHitRegion({id: "demo"});   // enable in flags in Chrome/Firefox
ctx.stroke();

x.addEventListener("click", function(e) {
  if (e.region && e.region === "demo") alert("Hit!");
})
<canvas id="canvas"></canvas>

Path: isPointInPath

The other techniques require one to manually implement a mechanism for hit-detection. One is by using isPointInPath(). You simply rebuild the paths you want to test, one by one, then run your (adjusted) x/y mouse coordinate against it:

var x=document.getElementById("canvas");
var ctx=x.getContext("2d");
generatePath();
ctx.stroke();

x.addEventListener("click", function(e) {
  var r = this.getBoundingClientRect(),
      x = e.clientX - r.left,
      y = e.clientY - r.top;
  
  // normally you would loop through your paths:
  generatePath();
  if (ctx.isPointInPath(x, y)) alert("Hit!");
})

function generatePath() {
  ctx.beginPath();          // reset path
  ctx.rect(20,20,150,100);  // add region to draw/test
}
<canvas id="canvas"></canvas>

Path: Path2D objects

For the latter example there is also the new Path2D objects which can hold a path on their own - the advantage here is that you don't need to rebuild the paths, just pass in the path object with x/y to the isPointInPath() method.

The problem is that Path2D is not supported in all browsers yet, but there is this polyfill that will fix that for you,

var x=document.getElementById("canvas");
var ctx=x.getContext("2d");

var path1 = new Path2D();
path1.rect(20,20,150,100);  // add rect to path object

ctx.stroke(path1);

x.addEventListener("click", function(e) {
  var r = this.getBoundingClientRect(),
      x = e.clientX - r.left,
      y = e.clientY - r.top;
  
  // normally you would loop through your paths objects:
  if (ctx.isPointInPath(path1, x, y)) alert("Hit!");
})
<canvas id="canvas"></canvas>

Manually check boundary

And of course, there is the old technique of using manual boundary checks. This will work in all browsers. Here the advisable thing to do is to create objects that holds the bounds and can also be used to render it. This typically limits you to rectangular areas - more complex shapes will require more complex algorithms (such as the isPointInPath() embeds).

var x=document.getElementById("canvas");
var ctx=x.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();

x.addEventListener("click", function(e) {
  var r = this.getBoundingClientRect(),
      x = e.clientX - r.left,
      y = e.clientY - r.top;
  
  // normally you would loop through your region objects:
  if (x >= 20 && x < 20+150 && y >= 20 && y < 20+100) alert("Hit!");
})
<canvas id="canvas"></canvas>



回答2:

Shapes and paths are drawn to the canvas as side-effects, so there is no element to add an event listener to; you could, however, add an event listener to the entire canvas or to an element that shares a location with the canvas, and when it is clicked then redraw the canvas with the rectangle, but red (or anything else changed). (make sure to clear the canvas before redrawing it with the .clearRect() method).



回答3:

If you draw something to a canvas, the shape that is drawn is not a javascript object, but rather changes the particular state that the canvas is in. Therefore, you cannot attach an event listener to it, and should instead attach the event to the canvas itself.

Your javascript could then check the co-ordinates of the click, and find whether or not it is inside the rectangle. Bear in mind that if you draw something on top of the rectangle or shape, the code will have to be adjusted to check the new area formed. You might also find it difficult to check the area if it is not a rectangle, but it will still be possible.

If you want to redraw the rectangle as red, you should repaint the canvas, changing the colour of the new rectangle that you redraw (the rectangle is not an object, so you cannot change the colour directly). This would also involve repainting all the other shapes on the canvas.