一、给Canvas元素绑定事件基础知识
由于事件只能达到Canvas元素这一层,所以,如果想进一步深入,识别点击发生在Canvas内部的哪一个图形上,就需要增加代码来进行处理。
基本思路是:给Canvas元素绑定事件,当事件发生时,检查事件对象的位置,然后检查哪些图形覆盖了该位置。只要鼠标点击在这个范围里,就可以视为点击了该矩形,也就可以手动触发矩形需要处理的点击事件。
第一步:封装一个获取事件对象位置的函数getEventPosition()
function getEventPosition(ev){
var x, y;
if (ev.layerX || ev.layerX == 0) {
x = ev.layerX;
y = ev.layerY;
} else if (ev.offsetX || ev.offsetX == 0) { // Opera
x = ev.offsetX;
y = ev.offsetY;
}
return {x: x, y: y};
}
//注:使用上面这个函数,需要给Canvas元素的position设为absolute。
第二步:给canvas添加点击事件
cvs = document.getElementById('mycanvas');
cvs.addEventListener('click', function(e){
//...
}, false);
第三步:判断事件对象的坐标是否在图形内:
cvs = document.getElementById('mycanvas');
ctx = canvas.getContext('2d');
ctx.rect(10, 10, 100, 100);
ctx.stroke();
ctx.isPointInPath(50, 50); //true
ctx.isPointInPath(5, 5); //false
注意:canvas中绘制多个图形时,isPointInPath()只能判断事件对象的位置是否在最后一个绘制的图形上,而之前绘制的图形已经无法判断
cvs = document.getElementById('mycanvas');
ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.rect(10, 10, 100, 100);
ctx.stroke();
ctx.isPointInPath(20, 20); //true
ctx.beginPath();
ctx.rect(110, 110, 100, 100);
ctx.stroke();
ctx.isPointInPath(150, 150); //true
ctx.isPointInPath(20, 20); //false
二.当canvas绘制多个图形时的点击事件
具体思路:当点击事件发生时,重绘所有图形,每绘制一个就使用isPointInPath方法,判断事件坐标是否在该图形覆盖范围内。
第一步:为了实现重绘,需要将图形的参数保存下来
arr = [
{x:10, y:10, width:100, height:100},
{x:110, y:110, width:100, height:100}
];
cvs = document.getElementById('mycanvas');
ctx = canvas.getContext('2d');
第二步:添加点击事件
cvs.addEventListener('click', function(e){
p = getEventPosition(e);
draw(p);
}, false);
第三步:每次点击时重绘图形
//图形绘制
function draw(p){
var who = [];//保存点击事件包含图形的index值
ctx.clearRect(0, 0, cvs.width, cvs.height);
arr.forEach(function(v, i){
ctx.beginPath();
ctx.rect(v.x, v.y, v.width, v.height);
ctx.stroke();
if(p && ctx.isPointInPath(p.x, p.y)){
//如果传入了事件坐标,就用isPointInPath判断一下
//如果当前环境覆盖了该坐标,就将图形的index放到数组里
who.push(i);
}
});
//根据数组中的index值,可以到arr数组中找到相应的元素。
return who;
}
在上面代码中,点击事件发生时draw方法会执行一次重绘,并在重绘过程中检查每一个图形是否覆盖了事件坐标,如果判断为真,则视为点击了该图形,并将该图形的index值放入数组,最后将数组作为draw方法的返回值。在这种处理机制下,如果Canvas里有N个图形,它们有一部分是重叠的,而点击事件恰巧发生在这个重叠区域上,那么draw方法的返回数组里会有N个成员。这时就有点类似事件冒泡的情况,数组的最后一个成员处于Canvas最上层,而第一个成员则在最下层,我们可以视为最上层的成员是e.target,而其他成员则是冒泡过程中传递到的节点。当然这只是最简单的一种处理方法,如果真要模拟DOM处理,还要给图形设置父子级关系。