Как добавить простой обработчик событий onClick к элементу холста?

Я опытный программист на Java, но впервые примерно за десять лет смотрю на некоторые вещи, связанные с JavaScript / HTML5. Я совершенно не понимаю, что должно быть самым простым.

В качестве примера я просто хотел что-то нарисовать и добавить к нему обработчик событий. Я уверен, что делаю что-то глупое, но я искал повсюду и ничего не предлагал (например, ответ на этот вопрос: Добавить свойство onclick для ввода с помощью JavaScript) работает. Я использую Firefox 10.0.1. Мой код следует. Вы увидите несколько прокомментированных строк, и в конце каждой будет описание того, что (или чего не происходит) происходит.

Какой здесь правильный синтаксис? Я схожу с ума!

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


person Jer    schedule 26.03.2012    source источник
comment
Используйте onclick вместо onClick   -  person noob    schedule 27.03.2012
comment
Чтобы уточнить, почему эти попытки не сработали ... Первая пара комментариев немедленно отображает предупреждение, потому что вы вызываете alert() непосредственно в <script>, вместо того, чтобы определять функцию, которая будет вызывать alert(). Остальные ничего не делают из-за использования заглавных букв onclick.   -  person LarsH    schedule 15.02.2016
comment
Вы можете использовать эту библиотеку jsfiddle.net/user/zlatnaspirala/fiddles, смотрит на bitbucket.org/nikola_l/visual-js. Вы получите много возможностей +   -  person Nikola Lukic    schedule 20.12.2016


Ответы (8)


Когда вы рисуете элемент canvas, вы просто рисуете растровое изображение в немедленном режиме.

Нарисованные элементы (фигуры, линии, изображения) не имеют представления, кроме используемых пикселей и их цвета.

Следовательно, чтобы получить событие click на canvas элементе (фигуре), вам необходимо зафиксировать события щелчка на canvas HTML-элементе и использовать математические вычисления, чтобы определить, какой элемент был нажат. при условии, что вы сохраняете ширину / высоту элементов и смещение по x / y.

Чтобы добавить событие click к вашему элементу canvas, используйте ...

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

Чтобы определить, какой элемент был нажат ...

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.

Этот код присоединяет событие click к элементу canvas, а затем подталкивает одну фигуру (в моем коде она называется element) в массив elements. Вы можете добавить сюда сколько угодно.

Цель создания массива объектов - чтобы мы могли позже запросить их свойства. После того, как все элементы были помещены в массив, мы перебираем и визуализируем каждый из них на основе их свойств.

Когда запускается событие click, код просматривает элементы и определяет, был ли щелчок по любому из элементов в массиве elements. Если это так, он запускает alert(), который можно легко изменить, например, удалить элемент массива, и в этом случае вам понадобится отдельная функция render для обновления canvas.


Для полноты картины, почему ваши попытки не сработали ...

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

Это присвоение возвращаемого значения alert() свойству onClick elem. Он немедленно вызывает alert().

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

В JavaScript ' и " семантически идентичны, лексер, вероятно, использует ['"] для кавычек.

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

Вы назначаете строку свойству onClick elem.

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

JavaScript чувствителен к регистру. Свойство onclick - это архаичный метод подключения обработчиков событий. Он позволяет прикрепить к свойству только одно событие, и событие может быть потеряно при сериализации HTML.

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

Опять ' === ".

person alex    schedule 26.03.2012
comment
Хм ... простите меня, если я неправильно понял - но разве я не фиксирую события щелчка на элементе холста? Я не уверен, что вы имеете в виду под каким элементом был нажат. На этой странице только один элемент, верно? - person Jer; 27.03.2012
comment
Он имел в виду, какой элемент на холсте - это какая форма, так сказать. - person Jovan Perovic; 27.03.2012
comment
@jperovic Именно так я написал свой ответ. В этом примере я просто назвал фигуру element. Может быть, это неправильное слово, это немного двусмысленно. - person alex; 27.03.2012
comment
Большое спасибо, хотя я не совсем понимаю последний. Я следую здесь примеру: developer.mozilla.org/en/DOM/element. onclick (используя анонимную функцию, которую они описывают), и она работает, даже когда я меняю диапазон на холст. Так что мне не составит труда постепенно преобразовать их пример в мой, чтобы увидеть, что я делаю не так. Спасибо за развернутый ответ! Объяснение того, почему мои различные попытки были ошибочными, было особенно полезным. - person Jer; 27.03.2012
comment
@Jer Не беспокойтесь, если у вас есть еще вопросы, напишите и напишите мне в Twitter, @alexdickson. - person alex; 27.03.2012
comment
Кто-нибудь использовал это для движущихся объектов холста? беспокоился, что он будет работать только на фиксированных позициях. хм, но если это произойдет во время щелчка, то все должно сработать. - person HashRocketSyntax; 09.04.2018
comment
@HashRocketSyntax Он должен работать нормально, просто обновите позиции ваших объектов в памяти и используйте эти определения для рендеринга из - person alex; 09.04.2018
comment
elem.onClick = () => alert('Hello world'); и elem.onClick => alert.bind(this, 'Hello world'); тоже работает - person Celsiuss; 24.06.2020

Наверное, очень поздно с ответом, но я только что прочитал это, готовясь к экзамену 70-480, и обнаружил, что это сработало -

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

Обратите внимание на событие как onclick вместо onClick.

Пример JS Bin.

person Soham Dasgupta    schedule 22.06.2018
comment
OP хочет добавить onclick к элементу внутри холста, но не сам холст - person Systems Rebooter; 14.01.2021

Я рекомендую следующую статью: Обнаружение области обращения для холста HTML5 и как прослушивать события щелчка на фигурах холста, который проходит через различные ситуации.

Однако он не распространяется на addHitRegion API, который должен быть лучшим способом (использование математических функций и / или сравнений весьма подвержено ошибкам). Этот подход подробно описан на сайте developer.mozilla.

person RUser4512    schedule 23.02.2018
comment
[addHitRegion] устарело. Хотя он все еще может работать в некоторых браузерах, его использование не рекомендуется, поскольку оно может быть удалено в любое время. Старайтесь не использовать его. - по включенной вами ссылке dev.moz, чтобы сохранить щелчок для других. - person Chris; 20.03.2020

В качестве альтернативы ответу Алексея:

Вы можете использовать рисунок SVG вместо рисунка холста. Там вы можете добавлять события прямо к нарисованным объектам DOM.

см. например:

Создание объекта изображения svg для щелчка с помощью onclick, избегая абсолютное позиционирование

person Goswin    schedule 14.10.2015
comment
извините за энтузиазм, просто у меня была проблема, для которой использование SVG является очевидным решением, и я не думал об этом до вашего комментария ^^ - person Elias Dorneles; 01.05.2020

Вы также можете поместить элементы DOM, такие как div, поверх холста, которые будут представлять ваши элементы холста и располагаться таким же образом.

Теперь вы можете прикрепить к этим блокам прослушиватели событий и выполнить необходимые действия.

person Sergei Basharov    schedule 31.03.2016
comment
для тупых iOS-программистов было бы здорово знать, как это сделать :) - person Fattie; 29.04.2021

В качестве еще одной дешевой альтернативы на несколько статичном холсте использование накладываемого элемента img с определением карты использования выполняется быстро и грязно. Особенно хорошо работает с элементами холста на основе многоугольников, такими как круговая диаграмма.

person TadLewis    schedule 11.04.2018

2021:

Чтобы создать отслеживаемый элемент, вы должны использовать new Path2D () метод.

Вы должны прослушивать события мыши на своем холсте, чтобы получить координаты точки (мыши), а затем использовать CanvasRenderingContext2D.isPointInPath() или CanvasRenderingContext2D.isPointInStroke(), чтобы точно проверить, находится ли мышь над вашим элементом.

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>

Пример с несколькими элементами:

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>

Если у вас есть список проверяемых динамических элементов, вы можете проверять их в цикле, например:

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>

Источники: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath & https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInStroke

person Le____    schedule 20.03.2021

Ответ Алекс довольно удобен, но при использовании поворота контекста может быть сложно отследить координаты x, y, поэтому я сделал Демонстрация, показывающая, как это отслеживать.

В основном я использую эту функцию и задаю ей угол и расстояние, пройденное этим ангелом, прежде чем рисовать объект.

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
    };
}
person Imran Bughio    schedule 24.01.2017