简体   繁体   English

将onclick和onmouseover添加到canvas元素

[英]Add onclick and onmouseover to canvas element

I want to add an onclick , onmouseover and an onmouseout events to individual shapes in a canvas element. 我想在canvas元素中为各个形状添加onclickonmouseoveronmouseout事件。

I have tried doing this with SVG in different ways and found no single method will work in all the major browsers. 我尝试过以不同的方式使用SVG,并发现没有一种方法可以在所有主流浏览器中使用。

Maybe, is there a simple way to add an onclick and probably other events to canvas shapes? 也许,是否有一种简单的方法可以将onclick和其他可能的事件添加到画布形状中?

Can someone please show me how to add an onclick ? 有人可以告诉我如何添加onclick吗?

Here is my code: 这是我的代码:

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")
}

Here is a barebones framework for adding events to individual canvas shapes 这是一个用于向各个画布形状添加事件的准系统框架

Here's a preview: http://jsfiddle.net/m1erickson/sAFku/ 这是一个预览: http//jsfiddle.net/m1erickson/sAFku/

Unlike SVG, after you draw a shape to canvas, there is no way to identify that shape. 与SVG不同,在将形状绘制到画布后,无法识别该形状。

On canvas, there are no individual shapes, there is just a canvas full of pixels. 在画布上,没有单独的形状,只有一个装满像素的画布。

To be able to identity and “use” any individual canvas shape, you need to remember all basic properties of that shape. 为了能够识别和“使用”任何单个画布形状,您需要记住该形状的所有基本属性。

Here are the properties necessary to identify a rectangle: 以下是标识矩形所需的属性:

  • x-position, x位置,
  • y-position, y位置
  • width, 宽度,
  • height. 高度。

You will also want to remember some basic styling properties of a rectangle: 您还需要记住矩形的一些基本样式属性:

  • fillcolor, 填色,
  • strokecolor, 则strokeColor,
  • strokewidth. strokewidth。

So here is how to create a rectangle “class” object that remembers all of it's own basic properties. 所以这里是如何创建一个矩形“类”对象,它记住它自己的所有基本属性。

If you're not familiar with the term "class", think of it as a "cookie-cutter" that we can use to define a shape. 如果您不熟悉术语“类”,请将其视为我们可以用来定义形状的“千篇一律”。

Then we can use the "cookie-cutter" class to create multiple copies of that shape. 然后我们可以使用“cookie-cutter”类来创建该形状的多个副本。

Even better ... classes are flexible enough to let us modify the basic properties of each copy that we make. 更好 ......类很灵活,可以让我们修改我们制作的每个副本的基本属性。

For rectangles, we can use our one class to make many, many rectangles of different widths, heights, colors and locations. 对于矩形,我们可以使用我们的一个类来制作许多不同宽度,高度,颜色和位置的矩形。

The key here is that we create classes because classes are Very Flexible and Reusable! 这里的关键是我们创建类,因为类是非常灵活和可重用的!

Here is our rect class that "remembers" all the basic info about any custom rectangle. 这是我们的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;
}

We can reuse this class to create as many new rectangles as we need...And we can assign different properties to our new rectangles to meet our needs for variety. 我们可以重用这个类来创建我们需要的新矩形......我们可以为新的矩形分配不同的属性,以满足我们对变化的需求。

When you create an actual rectangle (by filling in it's properties), every "cookie-cutter" copy of our class has its own private set of properties. 当您创建一个实际的矩形(通过填写它的属性)时,我们类的每个“cookie-cutter”副本都有自己的私有属性集。

When we use a "cookie-cutter" class to create 1+ actual rectangles to draw on the canvas, the resulting real rectangles are called "objects". 当我们使用“cookie-cutter”类创建1个以上的实际矩形以在画布上绘制时,生成的实际矩形称为“对象”。

Here we create 3 real rectangle objects from our 1 class. 在这里,我们从1个类中创建3个真正的矩形对象。 We have assigned each real object different width, height and colors. 我们为每个真实物体分配了不同的宽度,高度和颜色。

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);

Now let's give our class the ability to draw itself on the canvas by adding a draw() function. 现在让我们的类通过添加draw()函数在画布上绘制自己的能力。 This is where we put the canvas context drawing commands and styling commands. 这是我们放置画布上下文绘制命令和样式命令的地方。

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();
}

Here's how to use the draw() function to draw rectangles on the canvas. 以下是如何使用draw()函数在画布上绘制矩形。 Notice that we have 2 rectangle objects and we must execute .draw() on both of them for 2 rects to show on the canvas. 请注意,我们有2个矩形对象,我们必须在它们两个上执行.draw(),以便在画布上显示2个rects。

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();

Now give the rect class the ability to let us know if a point (mouse) is inside that rect. 现在让rect类能够告诉我们一个点(鼠标)是否在该矩形内。 When the user generates mouse events, we will use this isPointInside() function to test if the mouse is currently inside our 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);
}

Finally we can tie our rect class into the normal browser mouse event system. 最后,我们可以将rect类绑定到普通的浏览器鼠标事件系统中。

We ask jQuery to listen for mouse clicks on the canvas. 我们要求jQuery在画布上监听鼠标点击。 Then we feed that mouse position to the rect object. 然后我们将该鼠标位置提供给rect对象。 We use the rect's isPointInside() to report back if the click was inside the 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;

Well...that's how you "remember" canvas shapes & how you execute the code in your question! 嗯......这就是你如何“记住”画布形状以及如何在你的问题中执行代码!

alert("hello from blue square")

That's a barebones “class” that creates various rectangles and reports mouseclicks. 这是一个准确创建各种矩形并报告鼠标点击的准系统“类”。

You can use this template as a starting point to listen for all mouse-events on all kinds of canvas shapes. 您可以使用此模板作为起点来侦听各种画布形状上的所有鼠标事件。

Almost all canvas shapes are either rectangular or circular. 几乎所有的帆布形状都是矩形或圆形。

Rectangular Canvas Elements 矩形画布元素

  • Rectangle 长方形
  • Image 图片
  • Text 文本
  • Line (yes!) 线(是的!)

Circular Canvas Elements 圆形画布元素

  • Circle
  • Arc
  • Regular Polygon (yes!) 普通多边形(是的!)

Irregular Canvas Elements 不规则的画布元素

  • Curves (Cubic & Quad Beziers) 曲线(立方和四个贝塞尔曲线)
  • Path 路径

The isPointInside() would look like this for a circle: 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 );
}

Even irregularly shaped canvas elements can have isPointInside, but that usually gets complicated! 即使形状不规则的画布元素也可以有isPointInside,但这通常会变得复杂!

That's it! 而已!

Here is slightly enhanced code and a Fiddle: http://jsfiddle.net/m1erickson/sAFku/ 这是稍微增强的代码和小提琴: 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>

In short you cannot add listeners to shapes in a canvas because the shapes aren't exposed as objects. 简而言之,您无法将监听器添加到画布中的形状,因为形状不会作为对象公开。 The most straightforward way to implement this is to use aa single listener on the canvas and loop through all the objects drawn in the canvas to find the correct one. 实现此目的最直接的方法是在画布上使用单个侦听器并循环遍历画布中绘制的所有对象以找到正确的对象。

This answer explains how to implement this using the library Raphael which also gives you a lot of other benefits. 这个答案解释了如何使用Raphael库实现这一点,它还为您提供了许多其他好处。

If you don't want to use a library this is a very short example of doing it. 如果您不想使用库,这是一个非常简短的示例。

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);
        }
    }

NOTE: If you have a lot of objects this approach could be a bit slow. 注意:如果你有很多对象,这种方法可能会有点慢。 This can be combated by using a 2D spatial data structure. 这可以通过使用2D空间数据结构来对抗。

Added a more up-to-date answer : since this question was posted there are now two new techniques that can be used to detect local clicks in a canvas element: 添加了一个更新的答案 :自从发布此问题以来,现在有两种新技术可用于检测canvas元素中的本地点击:

  • Path2D : Paths can be stored on separate Path2D objects and checked using isPointInPath() Path2D :路径可以存储在单独的Path2D对象上,并使用isPointInPath()进行检查
  • addHitRegion : integrates with the event system which will allow you to check the event object itself of regions addHitRegion :与事件系统集成,允许您检查区域的事件对象本身

Path2D example 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)) { ... }

Read more about Path2D here . 在这里阅读更多关于Path2D的信息 A polyfill exists as well. 存在polyfill

addHitRegion example 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)
  }
});

Read more about addHitRegion() here . 在这里阅读更多关于addHitRegion() 信息

Note that it is still a bit early, both Firefox and Chrome supports this enabled via flags, other browsers will hopefully follow suit. 请注意,它仍然有点早,Firefox和Chrome都支持通过标志启用此功能,其他浏览器也希望效仿。

The main difference between Canvas and SVG is that Canvas does not retain information about shapes drawn other than resulting changes in the pixel array. Canvas和SVG之间的主要区别在于Canvas不保留有关绘制的形状的信息,而不是像素数组中的结果更改。

So one option would be to recognize the shapes by the corresponding pixel color value in the mouse click handler: 因此,一种选择是通过鼠标单击处理程序中的相应像素颜色值来识别形状:

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();
  }
}

If you want to track clicks for multiple objects with the same color using this approach, you'll need to slightly change the color for each shape to make it trackable. 如果要使用此方法跟踪具有相同颜色的多个对象的点击次数,则需要稍微更改每个形状的颜色以使其可跟踪。

This approach won't work when you add images from a different host because in this case the same origin policy will prevent getImageData. 当您从其他主机添加图像时,此方法将不起作用,因为在这种情况下,相同的源策略将阻止getImageData。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM