【HTML5】Canvas 内部元素添加事件处理
前言
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();
参考文章:
- Custom events in JavaScript
- javascript中constructor的作用
- 【优雅代码】深入浅出 妙用Javascript中apply、call、bind
- 深入理解JavaScript系列(4):立即调用的函数表达式
- Using Super Constructors Is Critical In Prototypal Inheritance In Javascript
【HTML5】Canvas 内部元素添加事件处理的更多相关文章
- Canvas 内部元素添加事件处理
目录 前言 自定义事件 有序数组 元素父类 事件判断 其他 立即执行函数 apply, call, bind addEventListener 传参 调用父类的构造函数 对象检测 isPointInP ...
- HTML5 canvas 内部元素事件响应
HTML5 canvas 内部元素事件响应 isPointInPath 只能拿当前上下文的路径 重画每个部分 都isPointInPath判断
- HTML5 Canvas 为网页添加文字水印
<!DOCTYPE html> <html> <body> <canvas id=" style="border:1px solid #d ...
- 用HTML5 Canvas为网页添加动态波浪背景
查看所有代码请去Github 本文出自 “UED” 博客:http://5344794.blog.51cto.com/5334794/1430877 <!DOCTYPE html> < ...
- 基于HTML5 Canvas 点击添加 2D 3D 机柜模型
今天又返回好好地消化了一下我们的数据容器 DataModel,这里给新手做一个典型的数据模型事件处理的例子作为参考.这个例子看起来很简单,实际上结合了数据模型中非常重要的三个事件处理的部分:属性变化事 ...
- Javascript为元素添加事件处理函数
document.getElementById("test").onclick = function(){ ... };
- HTML5 Canvas 的事件处理---转
DOM是Web前端领域非常重要的组成部分,不仅在处理HTML元素时会用到DOM,图形编程也同样会用到.比如SVG绘图,各种图形都是以DOM节点的形式插入到页面中,这就意味着可以使用DOM方法对图形进行 ...
- HTML5 Canvas(画布)实战编程初级篇:基本介绍和基础画布元素
欢迎大家阅读HTML5 Canvas(画布)实战编程初级篇系列,在这个系列中,我们将介绍最简单的HTML5画布编程.包括: 画布元素 绘制直线 绘制曲线 绘制路径 绘制图形 绘制颜色,渐变和图案 绘制 ...
- HTML5<canvas>标签:使用canvas元素在网页上绘制渐变和图像(2)
详细解释HTML5 Canvas中渐进填充的参数设置与使用,Canvas中透明度的设置与使用,结合渐进填充与透明度支持,实现图像的Mask效果. 一:渐进填充(Gradient Fill) Canva ...
随机推荐
- eclipse 配置scala开发环境
最近在学习spark相关知识.准备搭建eclipse开发环境.在安装过程中遇到的问题记录下来. 首先在scala网站上下载了scalaIDE:http://scala-ide.org/download ...
- 树莓派开发系列教程3——树莓派rasp-config配置(转)
经过前面两步我们的树莓派已经正常的工作起来了,但是在真正用它开发之前还需要进行一些列的配置以及软件的安装,这样开发起来才会得心应手,下面我们介绍一下常用的软件和服务 1.配置选项: 树莓派第一次使用的 ...
- componentsSeparatedByString,componentsJoinedByString,componentsSeparatedByCharactersInSet
将string字符串转换为array数组 NSArray *array = [Str componentsSeparatedByString:@","]; ==反向方法 将arr ...
- haploview出现"invalid affected status"的解决方法
haploview弹出这种错误是因为haploview的缺失值默认为0,而plink文件的缺失值一般用"-9"表示,当ped文件的缺失值为"-9"时,haplo ...
- node.js 基础学习笔记3 -http
http模块,其中封装了一个高效的HTTP服务器和一个建议的HTTP客户端 http.server是一个基于事件的HTTP服务器 http.request则是一个HTTP客户端工具,用户向服务器发送请 ...
- instancetype、id、NSObject的联系和区别
1.id和instancetype都能省去具体类型,提高代码的通用性.而NSObject *则没有这种功能. 2.instancetype只能用于方法的返回类型,而id用处和NSObject *类似. ...
- python学习之路-day5-模块
本节内容: 模块详解 1.模块定义 2.os&sys模块 3.time&datetime模块 4.random模块 5.shutil模块 6.shelve模块 7.configpars ...
- java 开发常用的Linux命令
1.查找文件 find / -name filename.txt 根据名称查找/目录下的filename.txt文件. find . -name "*.xml" 递归查找所有的xm ...
- u盘安装ubuntu
安装ubuntu 准备工作:首先需要安装好UltraISO(版本要比较新,有些老版本有照做后不能安装的错误) 准备好一个U盘. 下载好ubuntu光盘.(我用的12.04版) 步骤: 1.制作安装U盘 ...
- Java起源、发展历程、环境变量、第一个Java程序等【1】
若有不正之处,请多多谅解并欢迎批评指正,不甚感激. 请尊重作者劳动成果,转载请标明原文链接: 本文原创作者:pipi-changing 本文原创出处:http://www.cnblogs.com/pi ...