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

时间:2012-03-26 21:41:40

标签: javascript html5 canvas event-handling onclick

我是一名经验丰富的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>

8 个答案:

答案 0 :(得分:186)

当您绘制到canvas元素时,您只需在immediate mode中绘制位图。

绘制的元素(形状,线条,图像)除了使用的像素及其颜色外没有任何表示。

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

要向click元素添加canvas个事件,请使用...

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

确定点击的元素 ...

var elem = document.getElementById('myCanvas'),
    elemLeft = elem.offsetLeft,
    elemTop = elem.offsetTop,
    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(),可以很容易地修改它以执行诸如删除数组项之类的操作,在这种情况下,您需要单独的呈现函数来更新canvas

为了完整起见,为什么你的尝试没有成功......

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

这会将alert()的返回值分配给onClick的{​​{1}}属性。它会立即调用elem

alert()

在JavaScript中,elem.onClick = alert('hello world'); // displays alert without clicking '在语义上相同,词法分析器可能会使用"作为引号。

['"]

您正在为elem.onClick = "alert('hello world!')"; // does nothing, even with clicking 的{​​{1}}属性分配字符串。

onClick

JavaScript区分大小写。 elem属性是附加事件处理程序的古老方法。它只允许一个事件与属性相关联,并且在序列化HTML时可能会丢失事件。

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

再次,onclick

答案 1 :(得分:3)

我推荐以下文章:Hit Region Detection For HTML5 Canvas And How To Listen To Click Events On Canvas Shapes经历了各种各样的情况。

但是,它没有涵盖addHitRegion API,这必须是最好的方法(使用数学函数和/或比较非常容易出错)。这种方法详细on developer.mozilla

答案 2 :(得分:2)

答案可能很晚,但是我在准备70-480考试时就读了这篇文章,并发现它可以正常工作-

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

将事件通知为onclick,而不是onClick

JS Bin示例。

答案 3 :(得分:1)

作为alex答案的替代方案:

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

参见例如:

Making an svg image object clickable with onclick, avoiding absolute positioning

答案 4 :(得分:1)

您还可以将DOM元素(如div)放在画布顶部,该画布将代表您的画布元素并以相同的方式定位。

现在,您可以将事件侦听器附加到这些div并运行必要的操作。

答案 5 :(得分:1)

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

答案 6 :(得分:0)

Alex Answer非常简洁但是当使用上下文旋转时,很难跟踪x,y坐标,所以我已经制作了一个Demo来展示如何跟踪它。

基本上我正在使用这个功能&amp;给它角度&amp;在绘制物体之前在该天使中行进的距离量。

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

答案 7 :(得分:0)

作为某种静态画布上的另一种廉价替代品,使用带有usemap定义的重叠img元素是快速而又脏的。特别适用于基于多边形的画布元素,如饼图。