添加onclick和onmouseover到canvas元素

Add onclick and onmouseover to canvas element

本文关键字:canvas 元素 onmouseover onclick 添加      更新时间:2023-09-26

我想在画布元素中添加onclick, onmouseoveronmouseout事件到单个形状。

我已经尝试过用不同的方式使用SVG,发现没有一种方法可以在所有主要浏览器中工作。

也许,有一个简单的方法来添加一个onclick和其他事件可能画布形状?

谁能告诉我如何添加一个onclick ?

下面是我的代码:
canvas
{
  background:gainsboro;
  border:10px ridge green;
}
<canvas id="Canvas1"></canvas>
var c=document.getElementById("Canvas1");
var ctx=c.getContext("2d");
ctx.fillStyle="blue";
ctx.fillRect(10,10,60,60);
var ctx=c.getContext("2d");
ctx.fillStyle="red";
ctx.fillRect(80,60,60,60);
// these need an onclick to fire them up. How do I add the onclick
function blue()
{
  alert("hello from blue square")
}
function red()
{
  alert("hello from red square")
}

这是一个用于向单个画布形状添加事件的基本框架

预览:http://jsfiddle.net/m1erickson/sAFku/

与SVG不同的是,在画布上绘制形状之后,无法识别该形状。

在画布上,没有单独的形状,只有一个充满像素的画布。

为了能够识别和"使用"任何单独的画布形状,你需要记住该形状的所有基本属性。

下面是识别矩形所需的属性:

  • 的坐标,
  • 坐标,
  • 宽度,

您还需要记住矩形的一些基本样式属性:

  • fillcolor,
  • strokecolor,
  • strokewidth。

下面是如何创建一个矩形" class "对象来记住它自己所有的基本属性。

如果你不熟悉"类"这个术语,就把它想象成一个我们可以用来定义形状的"切饼机"。

然后我们可以使用"cookie-cutter"类来创建该形状的多个副本。

更好…类足够灵活,可以让我们修改每个副本的基本属性。

对于矩形,我们可以使用一个类来创建许多不同宽度、高度、颜色和位置的矩形。

这里的关键是我们创建类,因为类是非常灵活和可重用的!

这是我们的rect类,它"记住"任何自定义矩形的所有基本信息。

// the rect class 
function rect(id,x,y,width,height,fill,stroke,strokewidth) {
    this.x=x;
    this.y=y;
    this.id=id;
    this.width=width;
    this.height=height;
    this.fill=fill||"gray";
    this.stroke=stroke||"skyblue";
    this.strokewidth=strokewidth||2;
}

我们可以重用这个类来创建尽可能多的新矩形,因为我们需要…我们可以为我们的新矩形分配不同的属性,以满足我们对多样性的需求。

当你创建一个实际的矩形(通过填充它的属性)时,我们类的每个"千篇一律"的副本都有自己的私有属性集。

当我们使用"cookie-cutter"类创建1+个实际的矩形在画布上绘制时,生成的实际矩形被称为"对象"。

在这里,我们从我们的1类中创建了3个真正的矩形对象。我们给每个真实物体分配了不同的宽度、高度和颜色。

var myRedRect = new rect("Red-Rectangle",15,35,65,60,"red","black",3);
var myGreenRect = new rect("Green-Rectangle",115,55,50,50,"green","black",3);
var myBlueRect = new rect("Blue-Rectangle",215,95,25,20,"blue","black",3);

现在让我们通过添加draw()函数赋予类在画布上绘制自身的能力。这是我们放置画布上下文绘图命令和样式命令的地方。

rect.prototype.draw(){
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle=this.fill;
    ctx.strokeStyle=this.stroke;
    ctx.lineWidth=this.strokewidth;
    ctx.rect(x,y,this.width,this.height);
    ctx.stroke();
    ctx.fill();
    ctx.restore();
}
下面是如何使用draw()函数在画布上绘制矩形。请注意,我们有两个矩形对象,我们必须对它们执行。draw(),以便在画布上显示两个矩形。
var myRedRect = new rect("Red-Rectangle",15,35,65,60,"red","black",3);
myRedRect.draw();
var myBlueRect = new rect("Blue-Rectangle",125,85,100,100,"blue","orange",3);
myBlueRect.draw();

现在让rect类能够让我们知道一个点(鼠标)是否在rect内。当用户生成鼠标事件时,我们将使用这个isPointInside()函数来测试鼠标是否当前在我们的rect内。

// accept a point (mouseposition) and report if it’s inside the rect
rect.prototype.isPointInside = function(x,y){
    return( x>=this.x 
            && x<=this.x+this.width
            && y>=this.y
            && y<=this.y+this.height);
}

最后,我们可以将rect类绑定到普通的浏览器鼠标事件系统中。

我们要求jQuery监听画布上的鼠标点击。然后我们将鼠标位置提供给rect对象。我们使用rect的isPointInside()来报告点击是否在rect内。

// listen for click events and trigger handleMouseDown
$("#canvas").click(handleMouseDown);
// calc the mouseclick position and test if it's inside the rect
function handleMouseDown(e){
    // calculate the mouse click position
    mouseX=parseInt(e.clientX-offsetX);
    mouseY=parseInt(e.clientY-offsetY);
    // test myRedRect to see if the click was inside
    if(myRedRect.isPointInside(mouseX,mouseY)){
        // we (finally!) get to execute your code!
        alert("Hello from the "+myRedRect.id);
    }
}
// These are the canvas offsets used in handleMouseDown (or any mouseevent handler)
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;

嗯…这就是你如何"记住"画布形状的方法。如何执行问题中的代码!

alert("hello from blue square")

这是一个基本的"类",创建各种矩形和报告鼠标点击。

你可以使用这个模板作为起点来监听所有类型画布形状上的所有鼠标事件。

几乎所有的画布形状都是矩形或圆形的。

矩形画布元素

    矩形
  • 线(是的!)

圆形画布元素

  • 正多边形(是!)

不规则画布元素

  • 曲线(立方&四贝济耶)
  • 径>

对于一个圆,isPointInside()看起来像这样:

// check for point inside a circlular shape
circle.prototype.isPointInside = function(x,y){
    var dx = circleCenterX-x;
    var dy = circleCenterY-y;
    return( dx*dx+dy*dy <= circleRadius*circleRadius );
}

即使是不规则形状的画布元素也可以有isPointInside,但这通常会变得复杂!

就是这样!

这里是稍微增强的代码和一个Fiddle: http://jsfiddle.net/m1erickson/sAFku/

<!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>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var canvasOffset=$("#canvas").offset();
    var offsetX=canvasOffset.left;
    var offsetY=canvasOffset.top;
    //
    var rect = (function () {
        // constructor
        function rect(id,x,y,width,height,fill,stroke,strokewidth) {
            this.x=x;
            this.y=y;
            this.id=id;
            this.width=width;
            this.height=height;
            this.fill=fill||"gray";
            this.stroke=stroke||"skyblue";
            this.strokewidth=strokewidth||2;
            this.redraw(this.x,this.y);
            return(this);
        }
        //
        rect.prototype.redraw = function(x,y){
            this.x=x;
            this.y=y;
            ctx.save();
            ctx.beginPath();
            ctx.fillStyle=this.fill;
            ctx.strokeStyle=this.stroke;
            ctx.lineWidth=this.strokewidth;
            ctx.rect(x,y,this.width,this.height);
            ctx.stroke();
            ctx.fill();
            ctx.restore();
            return(this);
        }
        //
        rect.prototype.isPointInside = function(x,y){
            return( x>=this.x 
                    && x<=this.x+this.width
                    && y>=this.y
                    && y<=this.y+this.height);
        }

        return rect;
    })();

    //
    function handleMouseDown(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
      // Put your mousedown stuff here
      var clicked="";
      for(var i=0;i<rects.length;i++){
          if(rects[i].isPointInside(mouseX,mouseY)){
              clicked+=rects[i].id+" "
          }
      }
      if(clicked.length>0){ alert("Clicked rectangles: "+clicked); }
    }

    //
    var rects=[];
    //
    rects.push(new rect("Red-Rectangle",15,35,65,60,"red","black",3));
    rects.push(new rect("Green-Rectangle",60,80,70,50,"green","black",6));
    rects.push(new rect("Blue-Rectangle",125,25,10,10,"blue","black",3));
    //
    $("#canvas").click(handleMouseDown);

}); // end $(function(){});
</script>
</head>
<body>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

添加了一个最新的答案:自从这个问题被发布以来,现在有两种新技术可以用来检测画布元素中的本地点击:

  • Path2D:路径可以存储在单独的Path2D对象上,并使用isPointInPath()
  • 检查
  • addHitRegion:与事件系统集成,允许您检查区域的事件对象本身

Path2D例子
 var path1 = new Path2D();
 path1.rect(x1, y1, w, h);    // add sub-path to Path2D object
 var path2 = new Path2D();
 path2.rect(x2, y2, w, h);    // add sub-path to Path2D object
 // now we can iterate through the objects to check which path we
 // clicked without the need to rebuild each path as we did in the past
 if (ctx.isPointInPath(path1, x, y)) { ... }

在这里阅读更多关于Path2D的信息。也存在一个填充。

<标题> addHitRegion例子
// define a region using path
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.addHitRegion({id: "test"});
// now we can check in the event if the region was hit by doing:
canvas.addEventListener("mousemove", function(event){
  if(event.region) {
    // a region was hit, id can be used (see specs for options)
  }
});

点击这里了解更多关于addHitRegion()的信息。

请注意,这仍然有点早,Firefox和Chrome都支持通过标志启用此功能,其他浏览器有望效仿。

简而言之,您不能将侦听器添加到画布中的形状中,因为形状没有作为对象公开。实现这一点的最直接的方法是在画布上使用单个侦听器,并循环遍历画布中绘制的所有对象以找到正确的对象。

这个答案解释了如何使用库Raphael实现这一点,Raphael还为您提供了许多其他好处。

如果你不想使用库,这是一个非常简短的例子。

rects = [{ color : "blue", origin : { x : 10, y : 10 }, size : { width : 60, height: 60}},
         { color : "red", origin : { x : 80, y : 60 }, size : { width : 60, height: 60}}]
function onClick(event) {
    var length = rects.length;
    for (var i = 0; i < length; ++i) {
        var obj = rects[i];
        if (event.x > obj.x && event.x < obj.origin.x + obj.size.width &&
            event.y > obj.y && event.y < obj.origin.y + obj.size.height) {
            console.log("Object clicked: " + obj);
        }
    }

注意:如果你有很多对象,这种方法可能会有点慢。这可以通过使用2D空间数据结构来解决。

Canvas和SVG之间的主要区别是Canvas不保留关于绘制的形状的信息,除了导致像素数组的变化。

所以一个选项是通过鼠标点击处理程序中相应的像素颜色值来识别形状:

function onClick(event) {
  var data = ctx.getImageData(event.x, event.y, 1, 1);
  var red = data[0];
  var green = data[1];
  var blue = data[2];
  var color = red << 16 | green << 8 | blue;
  if (color == 0x0000ff) {
    blue();
  } else if (color == 0x0ff0000) {
    red();
  }
}

如果你想用这种方法跟踪相同颜色的多个对象的点击,你需要稍微改变每个形状的颜色,使其可跟踪。

当您从其他主机添加图像时,这种方法将不起作用,因为在这种情况下,相同的来源策略将阻止getImageData。