Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I add a simple onClick event handler to a canvas element?

People also ask

Does canvas support event handlers?

The Canvas event handling system closely follows the W3C DOM Event Model. If you have already used events in JavaScript and HTML most concepts of the Canvas event system should be already familiar to you.

Can you put Onclick on any element?

All HTML elements can have an onclick attribute.


When you draw to a canvas element, you are simply drawing a bitmap in immediate mode.

The elements (shapes, lines, images) that are drawn have no representation besides the pixels they use and their colour.

Therefore, to get a click event on a canvas element (shape), you need to capture click events on the canvas HTML element and use some math to determine which element was clicked, provided you are storing the elements' width/height and x/y offset.

To add a click event to your canvas element, use...

canvas.addEventListener('click', function() { }, false);

To determine which element was clicked...

var elem = document.getElementById('myCanvas'),
    elemLeft = elem.offsetLeft + elem.clientLeft,
    elemTop = elem.offsetTop + elem.clientTop,
    context = elem.getContext('2d'),
    elements = [];

// Add event listener for `click` events.
elem.addEventListener('click', function(event) {
    var x = event.pageX - elemLeft,
        y = event.pageY - elemTop;

    // Collision detection between clicked offset and element.
    elements.forEach(function(element) {
        if (y > element.top && y < element.top + element.height 
            && x > element.left && x < element.left + element.width) {
            alert('clicked an element');
        }
    });

}, false);

// Add element.
elements.push({
    colour: '#05EFFF',
    width: 150,
    height: 100,
    top: 20,
    left: 15
});

// Render elements.
elements.forEach(function(element) {
    context.fillStyle = element.colour;
    context.fillRect(element.left, element.top, element.width, element.height);
});​

jsFiddle.

This code attaches a click event to the canvas element, and then pushes one shape (called an element in my code) to an elements array. You could add as many as you wish here.

The purpose of creating an array of objects is so we can query their properties later. After all the elements have been pushed onto the array, we loop through and render each one based on their properties.

When the click event is triggered, the code loops through the elements and determines if the click was over any of the elements in the elements array. If so, it fires an alert(), which could easily be modified to do something such as remove the array item, in which case you'd need a separate render function to update the canvas.


For completeness, why your attempts didn't work...

elem.onClick = alert("hello world"); // displays alert without clicking

This is assigning the return value of alert() to the onClick property of elem. It is immediately invoking the alert().

elem.onClick = alert('hello world');  // displays alert without clicking

In JavaScript, the ' and " are semantically identical, the lexer probably uses ['"] for quotes.

elem.onClick = "alert('hello world!')"; // does nothing, even with clicking

You are assigning a string to the onClick property of elem.

elem.onClick = function() { alert('hello world!'); }; // does nothing

JavaScript is case sensitive. The onclick property is the archaic method of attaching event handlers. It only allows one event to be attached with the property and the event can be lost when serialising the HTML.

elem.onClick = function() { alert("hello world!"); }; // does nothing

Again, ' === ".


2021:

To create a trackable element you should use the new Path2D() method.

First listen to mouse events on your canvas to get the point (mouse) coordinates event.offsetX and event.offsetY then use CanvasRenderingContext2D.isPointInPath() or CanvasRenderingContext2D.isPointInStroke() to precisely check if the mouse is hover your element.

IsPointInPath:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// Create circle
const circle = new Path2D();
circle.arc(150, 75, 50, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill(circle);

// Listen for mouse moves
canvas.addEventListener('mousemove', function(event) {
  // Check whether point is inside circle
  if (ctx.isPointInPath(circle, event.offsetX, event.offsetY)) {
    ctx.fillStyle = 'green';
  }
  else {
    ctx.fillStyle = 'red';
  }

  // Draw circle
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fill(circle);
});
<canvas id="canvas"></canvas>

IsPointInStroke:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// Create ellipse
const ellipse = new Path2D();
ellipse.ellipse(150, 75, 40, 60, Math.PI * .25, 0, 2 * Math.PI);
ctx.lineWidth = 25;
ctx.strokeStyle = 'red';
ctx.fill(ellipse);
ctx.stroke(ellipse);

// Listen for mouse moves
canvas.addEventListener('mousemove', function(event) {
  // Check whether point is inside ellipse's stroke
  if (ctx.isPointInStroke(ellipse, event.offsetX, event.offsetY)) {
    ctx.strokeStyle = 'green';
  }
  else {
    ctx.strokeStyle = 'red';
  }

  // Draw ellipse
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fill(ellipse);
  ctx.stroke(ellipse);
});
<canvas id="canvas"></canvas>

Example with multiple elements:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

const circle = new Path2D();
circle.arc(50, 75, 50, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill(circle);

const circletwo = new Path2D();
circletwo.arc(200, 75, 50, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill(circletwo);

// Listen for mouse moves
canvas.addEventListener('mousemove', function(event) {
  // Check whether point is inside circle
  if (ctx.isPointInPath(circle, event.offsetX, event.offsetY)) {
    ctx.fillStyle = 'green';
    ctx.fill(circle);
  }
  else {
    ctx.fillStyle = 'red';
    ctx.fill(circle);
  }
  
    if (ctx.isPointInPath(circletwo, event.offsetX, event.offsetY)) {
    ctx.fillStyle = 'blue';
    ctx.fill(circletwo);
  }
  else {
    ctx.fillStyle = 'red';
    ctx.fill(circletwo);
  }
  
});
html {cursor: crosshair;}
<canvas id="canvas"></canvas>

If you have a list of dynamic elements to be checked, you can check them in a loop, like this:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
var elementslist = []

const circle = new Path2D();
circle.arc(50, 75, 30, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill(circle);

const circletwo = new Path2D();
circletwo.arc(150, 75, 30, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill(circletwo);

const circlethree = new Path2D();
circlethree.arc(250, 75, 30, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill(circlethree);

elementslist.push(circle,circletwo,circlethree)

document.getElementById("canvas").addEventListener('mousemove', function(event) {
event = event || window.event;
var ctx = document.getElementById("canvas").getContext("2d")

for (var i = window.elementslist.length - 1; i >= 0; i--){  

if (window.elementslist[i] && ctx.isPointInPath(window.elementslist[i], event.offsetX, event.offsetY)) {
document.getElementById("canvas").style.cursor = 'pointer';
    ctx.fillStyle = 'orange';
    ctx.fill(window.elementslist[i]);
return
} else {
document.getElementById("canvas").style.cursor = 'default';
    ctx.fillStyle = 'red';
    for (var d = window.elementslist.length - 1; d >= 0; d--){ 
    ctx.fill(window.elementslist[d]);
    }
}
}  

});
<canvas id="canvas"></canvas>

Sources: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath & https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInStroke


Probably very late to the answer but I just read this while preparing for my 70-480 exam, and found this to work -

var elem = document.getElementById('myCanvas');
elem.onclick = function() { alert("hello world"); }

Notice the event as onclick instead of onClick.

JS Bin example.


I recommand the following article : Hit Region Detection For HTML5 Canvas And How To Listen To Click Events On Canvas Shapes which goes through various situations.

However, it does not cover the addHitRegion API, which must be the best way (using math functions and/or comparisons is quite error prone). This approach is detailed on developer.mozilla


As an alternative to alex's answer:

You could use a SVG drawing instead of a Canvas drawing. There you can add events directly to the drawn DOM objects.

see for example:

Making an svg image object clickable with onclick, avoiding absolute positioning


You can also put DOM elements, like div on top of the canvas that would represent your canvas elements and be positioned the same way.

Now you can attach event listeners to these divs and run the necessary actions.