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

128

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

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 que se sugiere (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á varias líneas comentadas y al final de cada una encontrará 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
fuente
8
Usar en onclicklugar deonClick
noob
1
Para explicar por qué esos intentos no funcionaron ... Los primeros comentarios muestran una alerta de inmediato porque está llamando alert()directamente <script>, en lugar de definir una función que llame alert(). El resto no hace nada debido a la capitalización de onclick.
LarsH
Puede usar esta lib jsfiddle.net/user/zlatnaspirala/fiddles , mira bitbucket.org/nikola_l/visual-js . Obtendrá muchas funciones +
Nikola Lukic

Respuestas:

223

Cuando dibuja a un canvaselemento, simplemente está dibujando un mapa de bits en modo inmediato .

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

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

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 empuja una forma (llamada elementen mi código) a una elementsmatriz. Puede agregar tantos como desee aquí.

El propósito de crear una matriz de objetos es para que podamos consultar sus propiedades más adelante. Después de que todos los elementos se han insertado en la matriz, recorremos y representamos cada uno en función de 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í, dispara un 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 renderización separada para actualizar el canvas.


Para completar, por qué tus 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. Invoca de inmediato el alert().

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

En JavaScript, 'y "son semánticamente idénticos, el lexer probablemente usa ['"]para las 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 con la propiedad y el evento se puede perder al serializar el HTML.

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

Una vez más, ' === ".

alex
fuente
Hmm ... perdóname si estoy malentendido, pero ¿no estoy capturando eventos de clic en el elemento de lienzo? No estoy seguro de qué quiere decir con qué elemento se hizo clic. Solo hay un elemento en esta página, ¿verdad?
Jer
Se refería a "qué elemento dentro del lienzo", es decir, qué "forma", por así decirlo.
Jovan Perovic
1
Muchas gracias, aunque no tengo totalmente claro el último. Estoy siguiendo el ejemplo aquí: developer.mozilla.org/en/DOM/element.onclick (usando la función anónima que describen) y funciona, incluso cuando cambio el lapso a un lienzo. Así que no será difícil para mí transformar lentamente su ejemplo en el mío para ver qué estoy haciendo mal. ¡Gracias por la respuesta detallada! La explicación de por qué mis diversos intentos fueron incorrectos fue especialmente útil.
Jer
1
@Jer No se preocupe, si tiene más preguntas, siéntase libre de publicar y hacerme ping en Twitter, @alexdickson .
alex
1
@HashRocketSyntax Debería funcionar bien, solo actualice las posiciones de sus objetos en la memoria y use esas definiciones para renderizar
alex
4

Probablemente sea muy tarde para la respuesta, pero acabo de leer esto mientras me preparaba para mi 70-480examen y descubrí que esto funciona:

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

Observe el evento como en onclicklugar de onClick.

JS Bin ejemplo.

Soham Dasgupta
fuente
3

Recomiendo el siguiente artículo: Detección de región de hit para HTML5 Canvas y cómo escuchar eventos de clic en formas de Canvas que atraviesan varias situaciones.

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

RUser4512
fuente
"[ addHitRegion] está obsoleto. Aunque todavía puede funcionar en algunos navegadores, se desaconseja su uso, ya que podría eliminarse en cualquier momento. Intente evitar usarlo". - desde el enlace dev.moz que incluye, para guardar a otros un clic.
Chris
1

También puede colocar elementos DOM, como diven la parte superior del lienzo, que representarían los elementos del lienzo y se colocarían de la misma manera.

Ahora puede adjuntar oyentes de eventos a estos divs y ejecutar las acciones necesarias.

Sergei Basharov
fuente
1

Como otra alternativa barata en un lienzo algo estático, usar un elemento img superpuesto con una definición de mapa de uso es rápido y sucio. Funciona especialmente bien en elementos de lienzo basados ​​en polígonos como un gráfico circular.

TadLewis
fuente
0

Alex Answer es bastante ordenado, pero cuando se usa la rotación de contexto, puede ser difícil rastrear las coordenadas x, y, así que hice una demostración que muestra cómo hacer un seguimiento de eso.

Básicamente estoy usando esta función y le doy el ángulo y la cantidad de distancia recorrida en ese ángel antes de dibujar el objeto.

function rotCor(angle, length){
    var cos = Math.cos(angle);
    var sin = Math.sin(angle);

    var newx = length*cos;
    var newy = length*sin;

    return {
        x : newx,
        y : newy
    };
}
Imran Bughio
fuente