¿Cómo agrego un controlador de eventos onClick simple a un elemento de lienzo?

Resuelto Jer asked hace 12 años • 8 respuestas

Soy un programador Java experimentado, pero estoy viendo algunas cosas de JavaScript/HTML5 por primera vez en aproximadamente una década. Estoy completamente perplejo sobre lo que debería ser lo más simple que existe.

Como ejemplo, solo quería dibujar algo y agregarle un controlador de eventos. Estoy seguro de que estoy haciendo algo estúpido, pero he buscado por todas partes y nada de lo sugerido (por ejemplo, la respuesta a esta pregunta: Agregar propiedad onclick para ingresar con JavaScript ) funciona. Estoy usando Firefox 10.0.1. Mi código sigue. Verás varias líneas comentadas y al final de cada una hay una descripción de lo que (o lo que no) sucede.

¿Cuál es la sintaxis correcta aquí? ¡Me estoy volviendo loco!

<html>
<body>
    <canvas id="myCanvas" width="300" height="150"/>
    <script language="JavaScript">
        var elem = document.getElementById('myCanvas');
        // elem.onClick = alert("hello world");  - displays alert without clicking
        // elem.onClick = alert('hello world');  - displays alert without clicking
        // elem.onClick = "alert('hello world!')";  - does nothing, even with clicking
        // elem.onClick = function() { alert('hello world!'); };  - does nothing
        // elem.onClick = function() { alert("hello world!"); };  - does nothing
        var context = elem.getContext('2d');
        context.fillStyle = '#05EFFF';
        context.fillRect(0, 0, 150, 100);
    </script>

</body>

Jer avatar Mar 27 '12 04:03 Jer
Aceptado

Cuando dibujas en un canvaselemento, simplemente estás dibujando un mapa de bits en modo inmediato .

Los elementos (formas, líneas, imágenes) que se dibujan no tienen representación más allá de los píxeles que utilizan y su color.

Por lo tanto, para obtener un evento de clic en un canvas elemento (forma), necesita capturar eventos de clic en el canvaselemento HTML y usar algunas matemáticas para determinar en qué elemento se hizo clic, siempre que esté almacenando el ancho/alto y el desplazamiento x/y de los elementos. .

Para agregar un clickevento a su canvaselemento, use...

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

Para determinar en qué elemento se hizo clic...

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 .

Este código adjunta un clickevento al canvaselemento y luego inserta una forma (llamada en elementmi código) a una elementsmatriz. Puedes agregar tantos como desees aquí.

El propósito de crear una matriz de objetos es que podamos consultar sus propiedades más adelante. Después de que todos los elementos se hayan insertado en la matriz, recorremos y representamos cada uno según sus propiedades.

Cuando clickse activa el evento, el código recorre los elementos y determina si el clic se realizó sobre alguno de los elementos de la elementsmatriz. Si es así, activa un archivo alert(), que podría modificarse fácilmente para hacer algo como eliminar el elemento de la matriz, en cuyo caso necesitaría una función de procesamiento separada para actualizar el archivo canvas.


Para completar, por qué sus intentos no funcionaron...

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

Esto es asignar el valor de retorno de alert()a la onClickpropiedad de elem. Inmediatamente está invocando el alert().

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

En JavaScript, 'y "son semánticamente idénticos, el lexer probablemente los use ['"]para comillas.

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

Estás asignando una cadena a la onClickpropiedad de elem.

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

JavaScript distingue entre mayúsculas y minúsculas. La onclickpropiedad es el método arcaico de adjuntar controladores de eventos. Solo permite adjuntar un evento a la propiedad y el evento se puede perder al serializar el HTML.

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

De nuevo, ' === ".

alex avatar Mar 26 '2012 21:03 alex

2021:

Para crear un elemento rastreable en el lienzo HTML5, debe utilizar el nuevo método Path2D() .

Después de crear sus formas con new Path2D()un nombre para cada una de ellas, escuche los eventos táctiles o del mouse (como onclick, ondblclick, oncontextmenuo onmousemoveetc.) en su lienzo para obtener las coordenadas del punto event.offsetXy event.offsetYluego use CanvasRenderingContext2D.isPointInPath()o CanvasRenderingContext2D.isPointInStroke()para verificar con precisión si el mouse es desplazar tu elemento en ese evento.

EsPuntoEnPath:

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>
Expandir fragmento

EsPuntoEnTrazo:

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>
Expandir fragmento

Ejemplo con múltiples elementos:

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>
Expandir fragmento

Si tiene una lista de elementos dinámicos para verificar, puede verificarlos en un bucle, como este:

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 = elementslist.length - 1; i >= 0; i--){  

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

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


Fuentes :

  • https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D
  • https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath
  • https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInStroke
L777 avatar Mar 20 '2021 13:03 L777

Como alternativa a la respuesta de Alex:

Podrías usar un dibujo SVG en lugar de un dibujo de Canvas. Allí puede agregar eventos directamente a los objetos DOM dibujados.

ver por ejemplo:

Hacer que se pueda hacer clic en un objeto de imagen svg con onclick, evitando el posicionamiento absoluto

Goswin Rothenthal avatar Oct 14 '2015 12:10 Goswin Rothenthal

Recomiendo el siguiente artículo: Detección de región de acceso para lienzo HTML5 y cómo escuchar eventos de clic en formas de lienzo, que analiza varias situaciones.

Sin embargo, no cubre la addHitRegionAPI, que debe ser la mejor manera (usar funciones matemáticas y/o comparaciones es bastante propenso a errores). Este enfoque se detalla en desarrollador.mozilla

RUser4512 avatar Feb 23 '2018 10:02 RUser4512