如何向画布元素添加一个简单的 onClick 事件处理程序?

IT技术 javascript html canvas event-handling onclick
2021-02-08 03:20:58

我是一名经验丰富的 Java 程序员,但我在大约十年内第一次看到一些 JavaScript/HTML5 的东西。我完全被什么应该是有史以来最简单的事情难住了。

作为一个例子,我只是想画一些东西并向它添加一个事件处理程序。我确定我在做一些愚蠢的事情,但我已经搜索了所有内容,没有任何建议(例如这个问题的答案: Add onclick property to input with 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>

6个回答

当您绘制canvas元素时,您只是在立即模式下绘制位图

绘制元素(形状、线条、图像)除了它们使用的像素和颜色之外没有任何表示。

因此,要获取元素(形状)点击事件,您需要捕获HTML 元素上的点击事件并使用一些数学运算来确定点击了哪个元素,前提是您要存储元素的宽度/高度和 x/y 偏移.canvas canvas

要将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);
});​

js小提琴

此代码将一个click事件附加canvas元素,然后将一个形状(element在我的代码中称为 an 送到一个elements数组。您可以在此处添加任意数量的内容。

创建对象数组的目的是让我们稍后可以查询它们的属性。在所有元素都被推入数组后,我们循环遍历并根据它们的属性渲染每个元素。

click事件被触发时,代码循环遍历元素并确定点击是否在elements数组中的任何元素上如果是这样,它会触发一个alert(),可以很容易地修改它以执行诸如删除数组项之类的操作,在这种情况下,您需要一个单独的渲染函数来更新canvas.


为了完整起见,为什么您的尝试不起作用......

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

这是将 的返回值分配alert()给 的onClick属性elem它立即调用alert().

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

在 JavaScript 中,'and"在语义上是相同的,词法分析器可能使用['"]引号。

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

再次,' === "

他的意思是“画布中的哪个元素”——可以说是哪个“形状”。
2021-03-18 03:20:58
@HashRocketSyntax 它应该可以正常工作,只需更新对象在内存中的位置并使用这些定义进行渲染
2021-03-28 03:20:58
嗯...如果我误解了请原谅我 - 但我不是在画布元素上捕获点击事件吗?我不知道你的意思是什么东西被点击的元素。这个页面上只有一个元素,对吧?
2021-03-29 03:20:58
@Jer 不用担心,如果您有更多问题,请随时在 Twitter 上发帖和 ping 我,@alexdickson
2021-03-29 03:20:58
谢谢,非常感谢 - 虽然我对最后一个不太清楚。我正在遵循这里的示例: developer.mozilla.org/en/DOM/element.onclick(使用他们描述的匿名函数)并且它可以工作,即使我将跨度更改为画布。所以对我来说,慢慢地将他们的例子转化为我的例子并不难,看看我做错了什么。谢谢你的详细回复!为什么我的各种尝试都是错误的解释特别有帮助。
2021-03-30 03:20:58

2021 年:

要创建可跟踪元素,您应该使用新的 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

我认为 stackoverflow 应该用新的解决方案识别新的答案
2021-03-24 03:20:58

答案可能很晚了,但我只是在准备70-480考试时阅读了这个,并发现它起作用了-

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

注意事件 asonclick而不是onClick

JS Bin示例。

OP 想要添加onclick到画布内的元素,而不是画布本身
2021-04-08 03:20:58

我推荐以下文章:HTML5 Canvas 的命中区域检测以及如何侦听 Canvas Shapes 上的单击事件,它经历了各种情况。

但是,它不包括addHitRegionAPI,这一定是最好的方法(使用数学函数和/或比较很容易出错)。这种方法在 developer.mozilla 上有详细说明

“[ addHitRegion] 已过时。虽然它可能仍然在某些浏览器中工作,但不鼓励使用它,因为它可以随时被删除。尽量避免使用它。” - 来自您包含的 dev.moz 链接,以保存其他人的点击。
2021-04-07 03:20:58

作为亚历克斯回答的替代方案:

您可以使用 SVG 绘图而不是 Canvas 绘图。在那里您可以直接向绘制的 DOM 对象添加事件。

见例如:

使用 onclick 使 svg 图像对象可点击,避免绝对定位

很抱歉我的热情,只是我有一个问题,使用 SVG 是显而易见的解决方案,直到您发表评论我才想到它^^
2021-03-29 03:20:58