对于js一般的事件监听都是在dom元素的级别上进行的,而canvas内部的元素无法当成dom元素进行那个监听,也就是说对于浏览器,canvas内部有什么元素是根本不关心的。因此要实现canvas内部元素的事件监听,需要自己实现。
以鼠标事件为例,进行思考:
1.坐标转换
首先必须以canvas元素本身的事件监听为基础,这个用js的事件监听即可实现。问题是当dom事件触发后,我们如何判断是否触发了canvas内部元素事件,触发的是什么事件。
这时需要获取鼠标事件相对于canvas内的坐标。获取事件坐标在canvas时间监听的回调函数中的事件形参event中通过event.clientX和event.clientY获得,但是这只是相对整个页面窗口的坐标,需要转换成相对canvas的坐标。
canvas对象有一个方法getBoundClientRect(),返回的是canvas元素的边界框对象,设返回对象为bbox, 此对象的left与top属性即为canvas左上角点相对窗口的坐标,将事件坐标与canvas坐标相减,得到相对于canvas的坐标。
然而还有一个问题,canvas内部而言,1像素不一定等于浏览器中的1像素,这是因为,canvas作为dom元素有自身的宽高,canvas的内部也有宽高属性,这两个大小中有一个缩放关系,导致单位的不统一,之前得到的坐标是相对于
浏览器的单位比例而言的,要转换成canvas的单位比例,将横纵坐标各乘于(canvas.width / bbox.width)和(canvas.height / bbox.height)即可。
转换代码如下:
1 //传入canvas对象,与事件坐标参数
var windowToCanvas = function(canvas, x, y){ 2 var bbox; 3 bbox = canvas.getBoundingClientRect(); 4 return { 5 x: (x - bbox.left) * (canvas.width / bbox.width), 6 y: (y - bbox.top) * (canvas.height / bbox.height) 7 }; 8 };
2.监听器对象
对鼠标事件的监听是以坐标范围为基础的,一个事件对应一个坐标范围,若点击事件的坐标在该范围内,便出发对应的自定义事件。代码如下:
1 var canvasListener = function(canvas){ 2 var func; 3 this.canvas = canvas; 4 func = function(obj){ 5 var listener; 6 listener = obj; 7 return function(e){ 8 return listener.dealEvents(e); 9 }; 10 }; 11 this.canvas.click(func(this)); 12 this.events = []; 13 };
//添加自定义事件,参数为坐标范围(左上角坐标与范围宽高)和事件回调函数 14 canvasListener.prototype.addEvent = function(x, y, width, height, callback){ 15 var newEvent; 16 newEvent = { 17 x: x, 18 y: y, 19 width: width, 20 height: height, 21 callback: callback 22 }; 23 this.events.push(newEvent); 24 };
//点击事件触发时,判断该触发哪个自定义事件 25 canvasListener.prototype.dealEvents = function(e){ 26 var loc, i$, ref$, len$, event; 27 loc = windowToCanvas(this.canvas[0], e.clientX, e.clientY); 28 for (i$ = 0, len$ = (ref$ = this.events).length; i$ < len$; ++i$) { 29 event = ref$[i$]; 30 if (loc.x >= event.x && loc.x < event.x + event.width && loc.y >= event.y && loc.y < event.y + event.height) { 31 event.callback(); 32 } 33 } 34 };