Add onclick and onmouseover to canvas element

Mr SVG picture Mr SVG · May 18, 2013 · Viewed 26.4k times · Source

I want to add an onclick, onmouseover and an onmouseout events to individual shapes in a canvas element.

I have tried doing this with SVG in different ways and found no single method will work in all the major browsers.

Maybe, is there a simple way to add an onclick and probably other events to canvas shapes?

Can someone please show me how to add an 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")
}

Answer

markE picture markE · May 19, 2013

Here is a barebones framework for adding events to individual canvas shapes

Here's a preview: http://jsfiddle.net/m1erickson/sAFku/

Unlike SVG, after you draw a shape to canvas, there is no way to identify that shape.

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,
  • y-position,
  • width,
  • height.

You will also want to remember some basic styling properties of a rectangle:

  • fillcolor,
  • strokecolor,
  • 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.

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.

// 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.

When we use a "cookie-cutter" class to create 1+ actual rectangles to draw on the canvas, the resulting real rectangles are called "objects".

Here we create 3 real rectangle objects from our 1 class. 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. 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. Notice that we have 2 rectangle objects and we must execute .draw() on both of them for 2 rects to show on the canvas.

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. When the user generates mouse events, we will use this isPointInside() function to test if the mouse is currently inside our 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.

We ask jQuery to listen for mouse clicks on the canvas. Then we feed that mouse position to the rect object. We use the rect's isPointInside() to report back if the click was inside the 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:

// 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!

That’s it!

Here is slightly enhanced code and a 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>