Comment ajouter un simple gestionnaire d'événements onClick à un élément de canevas?

128

Je suis un programmeur Java expérimenté, mais je regarde des trucs JavaScript / HTML5 pour la première fois depuis une dizaine d'années. Je suis complètement perplexe sur ce qui devrait être la chose la plus simple qui soit.

À titre d'exemple, je voulais juste dessiner quelque chose et y ajouter un gestionnaire d'événements. Je suis sûr que je fais quelque chose de stupide, mais j'ai cherché partout et rien de ce qui est suggéré (par exemple, la réponse à cette question: Ajouter une propriété onclick pour entrer avec JavaScript ) ne fonctionne. J'utilise Firefox 10.0.1. Mon code suit. Vous verrez plusieurs lignes commentées et à la fin de chacune se trouve une description de ce qui (ou de ce qui ne se passe pas) se produit.

Quelle est la syntaxe correcte ici? Je deviens fou!

<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
la source
8
Utiliser à la onclickplace deonClick
noob
1
Pour expliquer pourquoi ces tentatives n'ont pas fonctionné ... Les premiers commentaires affichent immédiatement une alerte parce que vous appelez alert()directement <script>, au lieu de définir une fonction qui appellera alert(). Les autres ne font rien à cause de la capitalisation de onclick.
LarsH
Vous pouvez utiliser cette lib jsfiddle.net/user/zlatnaspirala/fiddles , regarde bitbucket.org/nikola_l/visual-js . Vous obtiendrez beaucoup de fonctionnalités +
Nikola Lukic

Réponses:

223

Lorsque vous dessinez sur un canvasélément, vous dessinez simplement une image bitmap en mode immédiat .

Les éléments (formes, lignes, images) qui sont dessinés n'ont aucune représentation en dehors des pixels qu'ils utilisent et de leur couleur.

Par conséquent, pour obtenir un événement de clic sur un canvas élément (forme), vous devez capturer les événements de clic sur l' canvasélément HTML et utiliser des mathématiques pour déterminer quel élément a été cliqué, à condition que vous stockiez la largeur / hauteur et le décalage x / y des éléments. .

Pour ajouter un clickévénement à votre canvasélément, utilisez ...

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

Pour déterminer quel élément a été cliqué ...

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 .

Ce code attache un clickévénement à l' canvasélément, puis pousse une forme (appelée an elementdans mon code) dans un elementstableau. Vous pouvez en ajouter autant que vous le souhaitez ici.

Le but de la création d'un tableau d'objets est que nous puissions interroger leurs propriétés plus tard. Une fois que tous les éléments ont été poussés sur le tableau, nous bouclons et rendons chacun d'eux en fonction de leurs propriétés.

Lorsque l' clickévénement est déclenché, le code parcourt les éléments et détermine si le clic était sur l'un des éléments du elementstableau. Si tel est le cas, il déclenche un alert(), qui pourrait facilement être modifié pour faire quelque chose comme supprimer l'élément de tableau, auquel cas vous auriez besoin d'une fonction de rendu distincte pour mettre à jour le canvas.


Par souci d'exhaustivité, pourquoi vos tentatives n'ont pas fonctionné ...

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

Il s'agit d'attribuer la valeur de retour de alert()à la onClickpropriété de elem. Il appelle immédiatement le alert().

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

En JavaScript, les 'et "sont sémantiquement identiques, le lexer utilise probablement ['"]pour les guillemets.

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

Vous affectez une chaîne à la onClickpropriété de elem.

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

JavaScript est sensible à la casse. La onclickpropriété est la méthode archaïque d'attachement de gestionnaires d'événements. Il n'autorise qu'un seul événement à être associé à la propriété et l'événement peut être perdu lors de la sérialisation du HTML.

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

Encore une fois, ' === ".

Alex
la source
Hmm ... pardonnez-moi si je ne comprends pas bien - mais est-ce que je ne capture pas les événements de clic sur l'élément de canevas? Je ne sais pas ce que vous entendez par l' élément sur lequel l'utilisateur a cliqué. Il n'y a qu'un seul élément sur cette page, non?
Jer
Il voulait dire "quel élément dans la toile" - c'est quelle "forme" pour ainsi dire.
Jovan Perovic
1
Merci beaucoup - même si je ne suis pas tout à fait clair sur le dernier. Je suis l'exemple ici: developer.mozilla.org/en/DOM/element.onclick (en utilisant la fonction anonyme qu'ils décrivent) et cela fonctionne, même lorsque je change la durée en canevas. Il ne sera donc pas difficile pour moi de transformer lentement leur exemple en le mien pour voir ce que je fais de mal. Merci pour la reponse detaillee! L'explication des raisons pour lesquelles mes différentes tentatives ont été fausses a été particulièrement utile.
Jer
1
@Jer Pas de soucis, si vous avez d'autres questions, n'hésitez pas à me poster et à m'envoyer un ping sur Twitter, @alexdickson .
alex
1
@HashRocketSyntax Cela devrait fonctionner correctement, il suffit de mettre à jour les positions de vos objets en mémoire et d'utiliser ces définitions pour effectuer le rendu de
alex
4

Probablement très tard pour la réponse, mais je viens de lire ceci en préparant mon 70-480examen, et j'ai trouvé que cela fonctionnait -

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

Notez l'événement comme onclickau lieu de onClick.

Exemple de JS Bin .

Soham Dasgupta
la source
3

Je recommande l'article suivant: Détection de zone de frappe pour le canevas HTML5 et comment écouter les événements de clic sur les formes de canevas qui passe par diverses situations.

Cependant, il ne couvre pas l' addHitRegionAPI, qui doit être le meilleur moyen (l'utilisation de fonctions mathématiques et / ou de comparaisons est assez sujette aux erreurs). Cette approche est détaillée sur developer.mozilla

RUser4512
la source
"[ addHitRegion] est obsolète. Bien qu'il puisse toujours fonctionner dans certains navigateurs, son utilisation est déconseillée car il peut être supprimé à tout moment. Essayez d'éviter de l'utiliser." - à partir du lien dev.moz que vous incluez, pour enregistrer les autres en un clic.
Chris
2

Comme alternative à la réponse d'Alex:

Vous pouvez utiliser un dessin SVG au lieu d'un dessin Canvas. Là, vous pouvez ajouter des événements directement aux objets DOM dessinés.

voir par exemple:

Rendre un objet image svg cliquable avec onclick, en évitant le positionnement absolu

Goswin
la source
1
désolé pour l'enthousiasme, c'est juste que j'ai eu un problème pour lequel utiliser SVG est la solution évidente, et je n'y avais pas pensé jusqu'à votre commentaire ^^
Elias Dorneles
1

Vous pouvez également placer des éléments DOM, comme divsur le dessus du canevas, qui représenteraient vos éléments de canevas et être positionnés de la même manière.

Vous pouvez maintenant attacher des écouteurs d'événements à ces div et exécuter les actions nécessaires.

Sergueï Basharov
la source
1

En tant qu'autre alternative bon marché sur un canevas quelque peu statique, l'utilisation d'un élément img superposé avec une définition usemap est rapide et sale. Fonctionne particulièrement bien sur les éléments de canevas basés sur des polygones comme un graphique à secteurs.

TadLewis
la source
0

Alex Answer est assez soigné, mais lors de l'utilisation de la rotation de contexte, il peut être difficile de tracer les coordonnées x, y, j'ai donc fait une démo montrant comment garder une trace de cela.

Fondamentalement, j'utilise cette fonction et je lui donne l'angle et la distance parcourue dans cet ange avant de dessiner l'objet.

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
la source