mouseenter/mouseleave事件和delegate方法的实现

众所周知,事件onmouseover和onmouseout有一个极其不好的问题,就是在绑定元素内部的子元素上滑动会反复触发事件,及执行绑定的方法。

而ie在很早的时候有提供了另一对事件:mouseenter和mouseleaver。顾名思义,就是只有当mouse滑进滑出绑定元素的时候,才会触发。

但是,这本来只是ie的私有属性,虽然已属于DOM3 Event草案当中,其他浏览器的支持率并不是很高,目前看来,Opera11.10已提供支持,而Firefox到10.0会提供支持,Webkit的暂无消息。
所以,如果想要用,我们得自己动手。

先搞一个通用的事件绑定函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

var addEvent = function( target,type,fn ) {
if(target.addEventListener)
{
target.addEventListener(type,fn,false);
}
else if(target.attachEvent)
{
target.attachEvent("on" + type,fn);
}
};
var removeEvent = function(target,type,fn ) {
if(target.addEventListener)
{
target.removeEventListener(type,fn,false);
}
else if(target.attachEvent)
{
target.detachEvent("on" + type,fn);
}
};

我们的mouseenter/leave是通过mouseover/out来实现的,只需屏蔽mouseover/out在元素内部触发时的事件传播即可。

 


为了解除绑定,我们设计了一个events._mouseFn来保存绑定的方法,在解除操作时读取对应的方法进行解绑。因为事件可以绑定多个方法,我们需要保存对应的方法,以便之后对应解除。
当然如果这里如果按面向对象的思路实现,就可以各自保存,而不需要保持在同一个events._mouseFn对象下。但每绑定一个事件,都需实例化一个对象,显得很多余,所以不采用面向对象的模式。

接下来是文章的第二部分,我们来实现下jquery中提供的delegate方法。
(delegate是live方法的扩展版。delegate是基于live的,live是基于bind的。在jquery1.7中又被封装进了on方法。1.7中的on方法是一个很辽阔的方法。其实封装的越厉害,效率就越差了,这也是为什么我们选择自己做简单封装的原因,而不是使用jquery已封装好的)。
这个方法可以将想要绑定在子级元素上的事件方法,委托绑定在其父级上,用事件传播机制来触发执行。
这么做的好处有:
1:如果子级有n个并列元素需要绑定,绑子级需要绑n次,而将其绑定在父级上则只需绑定一次,这是很高效的。
2:如果子级元素有动态增加的话,新增元素是没有绑定过任何事件方法的。而如果之前选择的是绑定其父级,就不会有这个问题。

 


这里的实现思路是这样的:如果触发事件的元素,是你想要绑定的元素的子级(当然他肯定已是委托实际绑定元素的子级),就执行绑定的事件方法,否则方法就不执行,看上去就像方法没绑定过一样。
同实现onmouseenter一样,我们也设计了一个events._deleFn来用于后面的解绑方法undelegate的实现。
另外针对ie,我们还解决了两个问题:

  1. 绑定方法内的this指向问题。我们用.apply执行绑定方法来解决。
  2. 使用apply调用绑定方法,就必须考虑如何解绑方法。原理和_mouseFn还有_deleFn一样。

在使用delegate时,我们同样遇到了mouseover/out的问题。
我们的解决方案是:不罗嗦,直接将mouseover/out处理成mouseenter/leave

 


最后,整理一下,封装一个支持mouseenter 和 mouseleave事件,delegate方法 及其他们的解除绑定的方法的 功能函数库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
window.events = {}
events._deleFn = {}; //保存delegate所绑定的方法
events._mouseFn ={}; //保存“onmouseenter”和“onmouseleave”所绑定的方法
events._ieFunc = {}; //由于保存在ie下绑定的方法

events._mouseHandle = function(fn){
/* 实现mouseenter/leave 的转换方法,符合条件时才会执行 */
var func = function(event){
var target = event.target;
var parent = event.relatedTarget; //在onmouseover/out操作中,相关的另一个节点
while( parent && parent != this ){
try{ parent = parent.parentNode; }
catch(e){break;}
}
/* 只有当相关节点的父级不会是绑定的节点时(即二者不是父子的包含关系),才调用fn,否则不做处理 */
( parent != this ) && (fn.call(target,event));
};
return func;
}

events._delegateHandle = function(obj,selector,fn){
/* 实现delegate 的转换方法,符合条件时才会执行 */
var func = function(event){
var event = event || window.event;
var target = event.srcElement || event.target;
var parent = target;
function contain(item,elmName){
if(elmName.split('#')[1]){ //by id
if(item.id && item.id === elmName.split('#')[1]) return true;
}
if(elmName.split('.')[1]){ //by class
if(hasClass(item, elmName.split('.')[1])) return true;
}
if(item.tagName == elmName.toUpperCase()) return true; //by tagname
return false;
}

while(parent){
/* 如果触发的元素,属于(selector)元素的子级。 */
if(obj == parent) return false; //触发元素是自己
if(contain(parent,selector)){
if(event.type == 'mouseover' || event.type == 'mouseout'){
/*
* 将mouseover/out直接处理成mouseenter/leave: 事件相关元素不属于绑定元素的子级,才绑定方法
*/
//事件相关元素。ie下使用toElement和fromElement,其他用relatedTarget。
var related = event.relatedTarget || ((event.type == 'mouseout') ? event.toElement : event.fromElement);
if(contain(target,selector) || contain(related,selector)) {
/* 如果,触发元素或相关元素属于绑定元素(selector)。执行方法 */
fn.call(obj,event);
return;
}
while( related && !contain(related,selector)){
related = related.parentNode;
}
/* 事件相关元素,不属于绑定元素(selector)的子级,执行方法 */
!contain(related,selector) && (fn.call(obj,event));
}else{
fn.call(obj,event);
}
return;
}
parent = parent.parentNode;
}
};
return func;
};

events.addEvent = function(target,type,fn){
if (!target) return false;
var add = function(obj){
if(obj.addEventListener){
if(obj.onmouseenter !== undefined){
//for opera11,firefox10。他们也支持“onmouseenter”和“onmouseleave”,可以直接绑定
obj.addEventListener(type,fn,false);
return ;
}
if(type=="mouseenter" || type=="mouseleave" ){
var eType = (type=="mouseenter") ? "mouseover" : "mouseout";
var fnNew = events._mouseHandle(fn);
obj.addEventListener(eType,fnNew,false);
/* 将方法存入events._mouseFn,以便以后remove */
if(!events._mouseFn[obj]) events._mouseFn[obj] = {};
if(!events._mouseFn[obj][eType]) events._mouseFn[obj][eType] = {};
events._mouseFn[obj][eType][fn] = fnNew;
}else{
obj.addEventListener(type,fn,false);
}
}else{
// for ie
if(!events._ieFunc[obj]) events._ieFunc[obj] = {};
if(!events._ieFunc[obj][type]) events._ieFunc[obj][type] = {};
events._ieFunc[obj][type][fn] = function(){
fn.apply(obj,arguments);
};
obj.attachEvent("on" + type,events._ieFunc[obj][type][fn]);
}
}
if(isDOMs(target)) {
for(var i=0, l = target.length; i < l; i++){
add(target[i])
}
}else{
add(target);
}
};

events.removeEvent = function(target,type,fn) {
if (!target) return false;
var remove = function(obj){
if(obj.addEventListener){
if(obj.onmouseenter !== undefined){
obj.removeEventListener(type,fn,false);
return ;
}
if(type=="mouseenter" || type=="mouseleave" ){
var eType = (type=="mouseenter") ? "mouseover" : "mouseout";
if(!events._mouseFn[obj][eType][fn]) return;
obj.removeEventListener(eType,events._mouseFn[obj][eType][fn],false);
events._mouseFn[obj][eType][fn]={};
}else{
obj.removeEventListener(type,fn,false);
}
}else{
//for ie
if(!events._ieFunc[obj] ||!events._ieFunc[obj][type] || !events._ieFunc[obj][type][fn]) return;
obj.detachEvent("on" + type, events._ieFunc[obj][type][fn],false);
events._ieFunc[obj][type][fn]={};
}
}
if(isDOMs(target)) {
for(var i=0, l = target.length; i < l; i++){
remove(target[i])
}
}else{
remove(target);
}
};

events.delegate = function(obj,selector,type,fn){
if (!obj || !selector) return false;
var fnNew = events._delegateHandle(obj,selector,fn);
events.addEvent(obj,type,fnNew);
/* 将绑定的方法存入events._deleFn,以便之后解绑操作 */
if(!events._deleFn[selector]) events._deleFn[selector] = {};
if(!events._deleFn[selector][type]) events._deleFn[selector][type] = {};
events._deleFn[selector][type][fn] = fnNew;
};

events.undelegate = function(obj,selector,type,fn){
if (!obj || !selector || !events._deleFn[selector]) return false;
var fnNew = events._deleFn[selector][type][fn];
if(!fnNew) return;
events.removeEvent(obj,type,fnNew);
events._deleFn[selector][type][fn] = null;
};