前言

canvas 没有提供为其内部元素添加事件监听的方法,因此如果要使 canvas 内的元素能够响应事件,需要自己动手实现。实现方法也很简单,首先获得鼠标在 canvas 上的坐标,计算当前坐标在哪些元素内部,然后对元素进行相应的操作。配合自定义事件,我们就可以实现为 canvas 内的元素添加事件监听的效果。

源码    演示

自定义事件

为了实现javascript对象的自定义事件,我们可以创建一个管理事件的对象,该对象中包含一个内部对象(当作map使用,事件名作为属性名,事件处理函数作为属性值,因为可能有个多个事件处理函数,所以使用数组存储事件处理函数),存储相关的事件。然后提供一个激发事件的函数,通过使用 call 方法来调用之前绑定的函数。下面是代码示例:

(function () {
cce.EventTarget = function () { this._listeners = {};
this.inBounds = false; }; cce.EventTarget.prototype = {
constructor: cce.EventTarget, // 查看某个事件是否有监听
hasListener: function (type) {
if (this._listeners.hasOwnProperty(type)) {
return true;
} else {
return false;
}
}, // 为事件添加监听函数
addListener: function (type, listener) {
if (!this._listeners.hasOwnProperty(type)) {
this._listeners[type] = [];
} this._listeners[type].push(listener);
cce.EventManager.addTarget(type, this);
}, // 触发事件
fire: function (type, event) {
if (event == null || event.type == null) {
return;
} if (this._listeners[event.type] instanceof Array) {
var listeners = this._listeners[event.type];
for (var i = 0, len = listeners.length; i < len; i++) {
listeners[i].call(this, event);
}
}
}, // 如果listener 为null,则清除当前事件下的全部事件监听
removeListener: function (type, listener) {
if (listener == null) {
if (this._listeners.hasOwnProperty(type)) {
this._listeners[type] = [];
cce.EventManager.removeTarget(type, this);
}
}
if (this._listeners[type] instanceof Array) {
var listeners = this._listeners[type];
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i] === listener) {
listeners.splice(i, 1);
if (listeners.length == 0)
cce.EventManager.removeTarget(type, this);
break;
}
}
} }
};
}());

在上面的代码中,EventManager 用来存储所有绑定了事件监听的对象,便于后面判断鼠标是否位于某个对象内部。如果一个自定义对象需要添加事件监听,只需要继承 EventTarget

有序数组

在判断触发某个事件的元素时,需要遍历所有绑定了该事件的元素,判断鼠标位置是否位于元素内部。为了减少不必要的比较,这里使用了一个有序数组,使用元素区域的最小 x 值作为比较值,按照升序排列。如果一个元素区域的最小 x 值大于鼠标的 x 值,那么就无需比较数组中该元素后面的元素。具体实现可以看 SortArray.js

元素父类

这里设计了一个抽象类,来作为所有元素对象的父类,该类继承了 EventTarget,并且定义了三个函数,所有子类都应该实现这三个函数。 具体代码如下所示:

(function () {

    // 抽象类,该类继承了事件处理类,所有元素对象应该继承这个类
// 为了实现对象比较,继承该类时应该同时实现compareTo, comparePointX 以及 hasPoint 方法。
cce.DisplayObject = function () {
cce.EventTarget.call(this);
this.canvas = null;
this.context = null; }; cce.DisplayObject.prototype = Object.create(cce.EventTarget.prototype);
cce.DisplayObject.prototype.constructor = cce.DisplayObject; // 在有序数组中会根据这个方法的返回结果将对象排序
cce.DisplayObject.prototype.compareTo = function (target) {
return null;
}; // 比较目标点的x值与当前区域的最小 x 值,结合有序数组使用,如果 point 的 x 小于当前区域的最小 x 值,那么有序数组中剩余
// 元素的最小 x 值也会大于目标点的 x 值,就可以停止比较。在事件判断时首先使用该函数过滤一下。
cce.DisplayObject.prototype.comparePointX = function (point) {
return null;
}; // 判断目标点是否在当前区域内
cce.DisplayObject.prototype.hasPoint = function (point) {
return false;
}; }());

事件判断

以鼠标事件为例,这里我们实现了 mouseovermousemovemouseout 三种鼠标事件。首先对 canvas 添加 mouseover事件,当鼠标在 canvas 上移动时,会时时对比当前鼠标位置与绑定了上述三种事件的元素的位置,如果满足了触发条件就调用元素的 fire方法触发对应的事件。下面是示例代码:

_handleMouseMove: function (event, container) {

    // 这里传入container 主要是为了使用 _windowToCanvas函数
var point = container._windowToCanvas(event.clientX, event.clientY); // 获得绑定了 mouseover, mousemove, mouseout 事件的元素对象
var array = cce.EventManager.getTargets("mouse");
if (array != null) {
array.search(point); // 鼠标所在的元素
var selectedElements = array.selectedElements; // 鼠标不在的元素
var unSelectedElements = array.unSelectedElements;
selectedElements.forEach(function (ele) {
if (ele.hasListener("mousemove")) {
var event = new cce.Event(point.x, point.y, "mousemove", ele);
ele.fire("mousemove", event);
} // 之前不在区域内,现在在了,说明鼠标进入了
if (!ele.inBounds) {
ele.inBounds = true;
if (ele.hasListener("mouseover")) {
var event = new cce.Event(point.x, point.y, "mouseover", ele);
ele.fire("mouseover", event);
}
}
}); unSelectedElements.forEach(function (ele) { // 之前在区域内,现在不在了,说明鼠标离开了
if (ele.inBounds) {
ele.inBounds = false;
if (ele.hasListener("mouseout")) {
var event = new cce.Event(point.x, point.y, "mouseout", ele);
ele.fire("mouseout", event);
}
}
});
}
}

其他

立即执行函数

诸如下面形式的函数称之为立即执行函数。

(function() {
// code
}());

使用立即执行函数的好处就是它限定了变量的作用域,使在立即执行函数中定义变量不会污染其他作用域,更加详细的讲解请看这里

apply, call, bind

这三个函数的使用类似于java 反射中的 Method.invoke,方法作为一个主体,将执行方法的对象作为参数传入到方法里。其中 apply 和 call 作用一样,调用后都会立即执行,只是接受参数的形式不同。

func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])

而 bind 会返回对应函数,不会立即执行,便于以后调用。 看下面的例子:

function aa() {
console.log(111);
console.log(this);
} var bb = aa.bind(Math);
bb();

更加详细的讲解请看这里

addEventListener 传参

如果给某个元素添加事件监听时需要传递参数,可以使用下面的方法

var i = 1;
aa.addEventListener("click", function() {
bb(i);
}, false);

调用父类的构造函数

使用 call 即可

Child = function() {
Parent.call(this);
}

对象检测

  • 判断对象为 null 或者 undefined

    // `null == undefined` 为true
    if (variable == null) {
    // code
    }
  • 判断对象是否有某个属性

    if(myObj.hasOwnProperty("<property name>")){
    alert("yes, i have that property");
    }
    // 或者
    if("<property name>" in myObj) {
    alert("yes, i have that property");
    }

isPointInPath

canvas中判断点是否在某个路径内部,可以用于多边形的检测。不过 isPointInPath 使用路径是最后一次绘制的图形,如果有多个图形需要判断,需要将前面的图形路径保存下来,判断时需要重新构造路径,不过不需要绘制,如下面

this.context.save();
this.context.beginPath(); //console.log(this.points);
this.context.moveTo(this.points[0].x, this.points[0].y); for (var i = 1; i < this.points.length; i++) {
this.context.lineTo(this.points[i].x, this.points[i].y);
} if (this.context.isPointInPath(target.x, target.y)) {
isIn = true;
}
this.context.closePath();
this.context.restore();

参考文章:

Canvas 内部元素添加事件处理的更多相关文章

  1. 【HTML5】Canvas 内部元素添加事件处理

    前言 canvas 没有提供为其内部元素添加事件监听的方法,因此如果要使 canvas 内的元素能够响应事件,需要自己动手实现.实现方法也很简单,首先获得鼠标在 canvas 上的坐标,计算当前坐标在 ...

  2. HTML5 canvas 内部元素事件响应

    HTML5 canvas 内部元素事件响应 isPointInPath 只能拿当前上下文的路径 重画每个部分 都isPointInPath判断

  3. Javascript为元素添加事件处理函数

    document.getElementById("test").onclick = function(){ ... };

  4. 给Jquery动态添加的元素添加事件

    给Jquery动态添加的元素添加事件 来源:[http://wangqixia.diandian.com/post/2011-05-10/6597866] 我想很多人都会向我一样曾经 被新元素的事件绑 ...

  5. [转载]给Jquery动态添加的元素添加事件

    原文地址:给Jquery动态添加的元素添加事件作者:小飞侠 我想很多人都会向我一样曾经 被新元素的事件绑定困惑很久也就是在页面加载完成后给元素绑定了事件,但又新增加的元素上却没有绑定任何事件. js的 ...

  6. SVG的内部事件添加

    SVG的内部事件添加: <%@ page language="java" contentType="text/html; charset=UTF-8" p ...

  7. 给DOM生成的元素添加事件

    问题:通过js给页面添加新元素,并给该元素添加绑定事件,但新添加的元素上却没有绑定任何事件. 常见例子:在处理表格的时候,每行行末有个删除按钮,如下图.点击删除按钮的时候删除这一行. //html部分 ...

  8. JS/jquery获取iframe内部元素和ifame中获取外部元素精华

    1.从外部获取iframe内部元素方法: js : window.frames['frame'].document.getElementById("imglist");   //f ...

  9. 用JQuery动态为选中元素添加/删除类

    在做一些tab页功能时,我们经常会见到如下样式: 即当选中一个元素时,在此元素下会添加相应的类,以示区别.今天就研究了一下如何用JQuery实现此效果. 1. HTML代码 <a id=&quo ...

随机推荐

  1. UIScrollView

    1.PPT介绍什么是UIScrollView 2.新建项目“大图片展示” * 尽量用storyboard,减少代码 * 设置contentSize属性 * 取消autolayout * 说明frame ...

  2. Android应用第一次安装成功点击“打开”后Home键切出应用后再点击桌面图标返回导致应用重启问题

    最近项目中遇到一个问题,用户第一次安装应用在系统的安装器安装完成界面有“完成”和“打开”两个按钮. 当用户点击“打开”按钮进入用户注册页面进行手机号验证码发送和验证码输入等操作界面,若此时用户点击Ho ...

  3. shell中括号的使用

    在这里我想说的是几种shell里的小括号,大括号结构和有括号的变量,命令的用法,如下: 1.${var} 2.$(cmd) 3.()和{} 4.${var:-string},${var:+string ...

  4. linux下解压被分割的zip文件

    形如被分割的一系列文件:linux.z01, linux.z02, linux.z03, linux.zip 直接右键解压是不行的. 首先合并文件:cat linux.* > linux_all ...

  5. 我与solr(二)--导入mysql数据库

    关于solr的搭建详见上一篇的随笔. 步骤1: 在webapps中solrhome下新建一个文件夹名字叫做mynode(名字不固定,可以随便取,但是这个名字在后面的配置中会有所关联.)然后在mynod ...

  6. angular中的 ng-change

    <!DOCTYPE HTML> <html ng-app="myApp"> <head> <meta http-equiv="C ...

  7. java和android及IOS对接RSA加密经验

    1.网上找的java生成RSA密钥对的例子,产生的字附串实际上是hax后和密钥串 你可以将他们当成静态字附串存在java代码里 2.android和java可以代码复用,IOS对接比较麻烦 3.IOS ...

  8. 【C-运算符】

    一.基本运算符 (1)赋值运算符:= a=2002;    //将值2002赋给变量a,动作从右到左 赋值运算左边必须指向一个存储位置(变量名——指针) (2)加法.减法运算符:+.—(二元或双目运算 ...

  9. 解决eclipse-helios中Errors running builder JavaScript Validator的问题(转)

    原文地址:http://hi.baidu.com/%B3%BF%D1%F4%C2%FE%B2%BD/blog/item/2528f6de3ca90955ccbf1a3f.html 最近下载了eclip ...

  10. ABP框架详解(三)Domain

    此处的Domain主要指Abp类库根目录下Domain文件夹.顾名思义该目录下是用来存放与领域实体,领域逻辑执行,存储,领域服务相关的内容. 1.Entities (1)为整个Abp框架后期开发的所有 ...