前言

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

事件判断

以鼠标事件为例,这里我们实现了 mouseover, mousemove, mouseout 三种鼠标事件。首先对 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();

参考文章:

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

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

    目录 前言 自定义事件 有序数组 元素父类 事件判断 其他 立即执行函数 apply, call, bind addEventListener 传参 调用父类的构造函数 对象检测 isPointInP ...

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

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

  3. HTML5 Canvas 为网页添加文字水印

    <!DOCTYPE html> <html> <body> <canvas id=" style="border:1px solid #d ...

  4. 用HTML5 Canvas为网页添加动态波浪背景

    查看所有代码请去Github 本文出自 “UED” 博客:http://5344794.blog.51cto.com/5334794/1430877 <!DOCTYPE html> < ...

  5. 基于HTML5 Canvas 点击添加 2D 3D 机柜模型

    今天又返回好好地消化了一下我们的数据容器 DataModel,这里给新手做一个典型的数据模型事件处理的例子作为参考.这个例子看起来很简单,实际上结合了数据模型中非常重要的三个事件处理的部分:属性变化事 ...

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

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

  7. HTML5 Canvas 的事件处理---转

    DOM是Web前端领域非常重要的组成部分,不仅在处理HTML元素时会用到DOM,图形编程也同样会用到.比如SVG绘图,各种图形都是以DOM节点的形式插入到页面中,这就意味着可以使用DOM方法对图形进行 ...

  8. HTML5 Canvas(画布)实战编程初级篇:基本介绍和基础画布元素

    欢迎大家阅读HTML5 Canvas(画布)实战编程初级篇系列,在这个系列中,我们将介绍最简单的HTML5画布编程.包括: 画布元素 绘制直线 绘制曲线 绘制路径 绘制图形 绘制颜色,渐变和图案 绘制 ...

  9. HTML5<canvas>标签:使用canvas元素在网页上绘制渐变和图像(2)

    详细解释HTML5 Canvas中渐进填充的参数设置与使用,Canvas中透明度的设置与使用,结合渐进填充与透明度支持,实现图像的Mask效果. 一:渐进填充(Gradient Fill) Canva ...

随机推荐

  1. eclipse 配置scala开发环境

    最近在学习spark相关知识.准备搭建eclipse开发环境.在安装过程中遇到的问题记录下来. 首先在scala网站上下载了scalaIDE:http://scala-ide.org/download ...

  2. 树莓派开发系列教程3——树莓派rasp-config配置(转)

    经过前面两步我们的树莓派已经正常的工作起来了,但是在真正用它开发之前还需要进行一些列的配置以及软件的安装,这样开发起来才会得心应手,下面我们介绍一下常用的软件和服务 1.配置选项: 树莓派第一次使用的 ...

  3. componentsSeparatedByString,componentsJoinedByString,componentsSeparatedByCharactersInSet

    将string字符串转换为array数组 NSArray  *array = [Str componentsSeparatedByString:@","]; ==反向方法 将arr ...

  4. haploview出现"invalid affected status"的解决方法

    haploview弹出这种错误是因为haploview的缺失值默认为0,而plink文件的缺失值一般用"-9"表示,当ped文件的缺失值为"-9"时,haplo ...

  5. node.js 基础学习笔记3 -http

    http模块,其中封装了一个高效的HTTP服务器和一个建议的HTTP客户端 http.server是一个基于事件的HTTP服务器 http.request则是一个HTTP客户端工具,用户向服务器发送请 ...

  6. instancetype、id、NSObject的联系和区别

    1.id和instancetype都能省去具体类型,提高代码的通用性.而NSObject *则没有这种功能. 2.instancetype只能用于方法的返回类型,而id用处和NSObject *类似. ...

  7. python学习之路-day5-模块

    本节内容: 模块详解 1.模块定义 2.os&sys模块 3.time&datetime模块 4.random模块 5.shutil模块 6.shelve模块 7.configpars ...

  8. java 开发常用的Linux命令

    1.查找文件 find / -name filename.txt 根据名称查找/目录下的filename.txt文件. find . -name "*.xml" 递归查找所有的xm ...

  9. u盘安装ubuntu

    安装ubuntu 准备工作:首先需要安装好UltraISO(版本要比较新,有些老版本有照做后不能安装的错误) 准备好一个U盘. 下载好ubuntu光盘.(我用的12.04版) 步骤: 1.制作安装U盘 ...

  10. Java起源、发展历程、环境变量、第一个Java程序等【1】

    若有不正之处,请多多谅解并欢迎批评指正,不甚感激. 请尊重作者劳动成果,转载请标明原文链接: 本文原创作者:pipi-changing 本文原创出处:http://www.cnblogs.com/pi ...