目录
五、DOM事件
事件,是用户或浏览器自身执行的某种动作。当在网页上操作时,比如点击一个DOM元素、键盘按下一个键、输入框输入内容、页面加载完成等,都会触发DOM事件。
1、事件流
DOM事件流即DOM事件处理执行的过程,为了判断页面的哪一部分会拥有特定的事件。W3C对DOM事件流作了规范的定义,主要分为三块内容。
capture phase:捕获过程,从DOM树最顶端window对象开始,往下捕获,直到捕获到触发该事件的节点的父元素。
target phase:目标节点,即事件处理节点的事件触发过程。
bubble phase:冒泡过程,从当前事件节点的父结点开始,冒泡到顶层的window对象。
注册到这个事件流的任意一个节点,都会被触发。
不是所有事件都有这三个过程,有些事件没有冒泡,如页面的node事件。
2、事件注册与触发
主体都是事件对象的DOM元素。
(1)事件注册
eventTarget.addEventListener(type,listener[,useCapture])
参数为事件类型、事件处理函数、是否是捕获过程(可选参数)。默认false处理冒泡过程。
大多数情况下,将事件处理函数添加到事件流的冒泡阶段,可以最大限度地兼容各种浏览器,最好只在需要在事件到达目标之前截获它的时候将事件处理函数添加到捕获阶段。
这里的事件处理函数在其依附元素的作用域中执行。
(2)取消事件注册
eventTarget.removeEventListener(type,listener[,useCapture])
添加的匿名函数无法移除。
(3)事件触发
点击元素、按下按键等都可能触发,使用程序代码触发如下。
eventTarget.dispatchEvent(type)
var elem=document.getElementById('div1');
var clickHandler=function(event){
//事件处理函数,当事件触发的时候,引擎中调用该函数
}
elem.addEventListener('click',clickHandler,false); //在一个节点可处理多个事件处理函数,先添加先处理
elem.onclick=clickHandler; //冒泡过程,但事件处理函数只有一个
elem.removeEventListener('click',clickHandler,false);
elem.onclick=null; //不建议用这种方式
elem.dispatchEvent('click');
(4)浏览器兼容型(IE8-)
事件的注册与取消attachEvent()/detachEvent(),事件触发fireEvent(e)。没有捕获阶段。
这里的事件处理函数在全局作用域运行,其中的this等于window。且这些事件处理函数以添加它们的相反顺序被触发。
var addEvent=document.addEventListener ?
function(elem,type,listener,useCapture){
elem.addEventListener(type,listener,useCapture);
} :
function(elem,type,listener,useCapture){
elem.attachEvent('on'+type,listener);
};
var delEvent=document.removeEventListener ?
function(elem,type,listener,useCapture){
elem.removeEventListener(type,listener,useCapture);
} :
function(elem,type,listener,useCapture){
elem.detachEvent('on'+type,listener);
};
3、事件对象
当事件被触发时,会调用事件处理函数,调用时引擎会传入对象,包含当前事件状态的信息,这就是事件对象。在IE低版本中,一个事件的event对象不是直接通过函数传入,而是放在window对象上。编程过程中会用到该事件对象的属性和方法。
如当用鼠标点击元素时,触发click事件,调用clickHandler函数,传入event对象,可能包含鼠标的XY坐标、shift是否按下等等。
var elem=document.getElementById('div1');
var clickHandler=function(event){
event=event||window.event;
//TO DO
}
addEvent(elem,'click',clickHandler,false);
事件对象通用的属性和方法。
type:事件类型,对事件处理函数的封装switch-case;
target:事件触发的节点,IE低版本使用srcElement属性;
currentTarget:要处理一个事件时,可以把该事件注册在target或其父节点,currentTarget是注册该事件的节点的元素。只有当事件处于目标阶段,才等于target。
<body>
<input type="button" id="myBtn" />
</body>
var btn=document.getElementById("myBtn");
btn.onclick=function(event){
alert(event.currentTarget===this); //true
alert(event.target===this); //true
}
document.body.onclick=function(event){
alert(event.currentTarget===this); //true
alert(this===document.body); //true
alert(event.target===btn); //true
}
stopPropagation():阻止传播,阻止事件传到父节点。event.cancelBubble=true(IE)。
stopImmediatePropagation():阻止事件传到父节点,并阻止当前节点的后续事件。
preventDefault():阻止默认行为(默认行为:点击链接则打开并转到链接,点击提交表单会跳转,双击一段文字文字被选中等)。event.returnValue=false(IE)。
//省略了css
<div class="father">
<div class="son"></div>
</div>
<a href="http:www.baidu.com">百度</a>
$(function(){
$(".father").click(function(){
alert("father");
});
$(".son").click(function(event){
alert("son");
//阻止冒泡return false;或
event.stopPropagation();
});
$("a").click(function(event){
alert("弹出注册框");
//阻止默认行为return false;或
event.preventDefault();
});
});
4、事件分类
(1)MouseEvent鼠标事件
事件类型 | 说明 | 元素 | 是否冒泡 | 默认事件 |
click | 单击主鼠标按钮或按下回车键 | Element | YES | focus/activation |
dbclick | 双击主鼠标按钮 | YES | focus/activation/select | |
mousedown | 按下任意鼠标按钮 | YES | drag/scroll/text selection | |
mouseup | 释放鼠标按钮 | YES | content menu | |
mousemove | 鼠标在元素内部移动重复触发 | YES | None | |
mouseover | 进入元素 | YES | None | |
mouseout | 离开元素 | YES | None | |
mouseenter | 进入元素 | NO | None | |
mouseleave | 离开元素 | NO | None |
mouseover和mouseout移动到后代元素会触发;mouseenter和mouseleave移动到后代元素上不会触发。
属性:clientX, clientY, screenX, screenY,为了处理位置信息,定义鼠标事件发生时,鼠标在页面上的位置。
ctrlKey, shiftKey, altKey, metaKey(Windows键或Cmd键),当事件被触发时,如果键盘上的键被按下,则对应的属性值为true。
button,属性值为0,1,2,代表按下鼠标左键、中间键或右键。
顺序:鼠标事件非常灵敏,会同时触发很多鼠标事件。
从元素A上方移过mousemove->mouseover(A)->mouseenter(A)->mousemove(A)->mouseout(A)->mouseleave(A)
点击元素mousedown->[mousemove]->mouseup->click
//例子--拖拽div,窗体拖动
//忽略了细节,如元素不能被选中,拖动不能超出屏幕范围
<head>
<style type="text/css">
#div1{
position:absolute;top:0;left:0;
border:1px solid #000;
width:100px;height:100px;
}
</style>
</head>
<body>
<div id="div1"></div>
<script>
var elem=document.getElementById("div1");
var clientX,clientY,moving;
//按下鼠标,获取当前位置,moving设为true
var mouseDownHandler=function(event){
event=event||window.event;
clientX=event.clientX;
clientY=event.clientY;
moving=!0;
}
var mouseMoveHandler=function(event){
if(!moving) return; //如果moving为false则返回
event=event||window.event;
var newClientX=event.clientX,//移动后位置
newClientY=event.clientY;
var left=parseInt(elem.style.left)||0,
top=parseInt(elem.style.top)||0;
elem.style.left=left+(newClientX-clientX)+'px';//更改元素位置
elem.style.top=top+(newClientY-clientY)+'px';
clientX=newClientX;//当前位置
clientY=newClientY;
}
//释放鼠标按钮,moving设为false
var mouseUpHandler=function(event){
moving=!1;
}
elem.addEventListener('mousedown',mouseDownHandler);
elem.addEventListener('mousemove',mouseMoveHandler);
elem.addEventListener('mouseup',mouseUpHandler);
</script>
</body>
(2)WheelEvent滚轮事件
继承自鼠标事件,拥有其属性和方法。属性:deltaX, deltaY, deltaZ鼠标滚轮在X、Y、Z方向上产生的偏移量;deltaMode指定delta值的单位。
事件类型 | 说明 | 元素 | 是否冒泡 | 默认事件 |
wheel | 滚动鼠标滚轮或 在垂直方向上滚动页面 | Element | YES | scroll or zoom document |
(3)FocusEvent焦点事件
处理元素获得或失去焦点。属性:relatedTarget,一个元素获得焦点必然另一个元素失去焦点,对于focus、focusin,该属性指向失去焦点的元素。
事件类型 | 说明 | 元素 | 是否冒泡 | 默认事件 |
focus | 获得焦点 | Window Element | NO | None |
blur | 失去焦点 | NO | None | |
focusin | 获得焦点前 | NO | None | |
focusout | 失去焦点前 | NO | None |
(4)InputEvent输入事件
在输入框输入内容时不断触发该事件。onpropertychange(IE)。
事件类型 | 说明 | 元素 | 是否冒泡 | 默认事件 |
beforeinput | 输入内容前触发 | Element | YES | update DOM/Element |
input | 正在输入不断触发 | YES | None |
(5)KeyboardEvent键盘事件
事件类型 | 说明 | 元素 | 是否冒泡 | 默认事件 |
keydown | 按下键 | Element | YES | beforeinput/input focus/blur activation |
keyup | 释放键 | YES | None |
属性:key按键,值为一个字符串;
code按键,值为一个字符串;
ctrlKey, shiftKey, altKey, metaKey,当事件被触发时,如果键盘上的键被按下,则对应的属性值为true;
repeat长按一个键,值为true;
keyCode, charCode, which获得按键的ASCII码。
(6)UIEvent
指不一定与用户操作有关的事件。多数这些事件与window对象或表单控件相关。
事件 类型 | 说明 | 元素 | 是否 冒泡 | 默认 事件 | 元素例子 |
load | 加载完成 | Window/Document /Element | NO | None | window/image/iframe |
unload | window退出 | NO | None | window | |
error | 加载错误 | Window/Element | NO | None | window/image |
select | 文本被选中 | Element | NO | None | input/textarea |
abort | 退出 | Window/Element | NO | None | window/image |
resize | 修改窗体大小 | Window/Element | NO | None | window/iframe |
scroll | 页面发生滚动 | Document/Element | NO/YES | None | document/div |
window对象:load页面所有请求都加载完成;unload离开或关闭当前页面;error页面加载出现异常;abort退出。
image元素:load图片加载完成,在该事件中可得知图片的长宽;error图片加载异常,如给图片赋值错误的url地址;abort退出,如图片加载时按esc。
//当src地址加载失败,显示默认图片
<image alt="photo" src="http://www.163.com/photo.jpg"
onerror="this.src='http://www.163.com/default.jpg'"/>
(7)移动设备事件
分为基础事件(PC事件、触摸事件)和手势(触摸事件的组合),触摸事件:touchstart, touchend, touchmove, touchcancel。
因为要支持多指操作,一个指头按下去,浏览器要处理另外一个指头发生组合行为的判断时间,移动端click有300ms的延时,有卡顿感。fastclick即tap,点击后马上触发行为,通过基础事件封装模拟,touchstart和touchend间隔时间短且坐标在小范围内(几像素)发生移动。此外,向左向右滑都可以封装模拟。
5、事件代理
利用事件冒泡,在DOM树中尽可能最高的层次上只指定一个事件处理程序,就可以管理某一类型的所有事件。添加到页面的事件处理程序需要不断与DOM节点交互,每访问一次DOM就会引起浏览器重排,影响页面的整体运行性能,为了高效处理事件,使用事件代理,将多次与DOM的操作减少为一次。
更多细节参考:https://www.cnblogs.com/liugang-vip/p/5616484.html。
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
var list=document.getElementById("myLinks");
EventUtil.addHandler(list,"click",function(event)){
event=event||window.event;
var target=event.target||event.srcElement;
switch(target.id){
case "goSomewhere":
location.href="http://www.wrox.com";
break;
case "doSomething":
document.title="I changed the document's title";
break;
case "sayHi":
alert("hi");
break;
}
}
对于事件代理,innerHTML修改不会影响事件处理,因为是冒泡到祖先节点进行事件处理。有时新增的子元素也带有事件。
最适合采用事件代理技术的事件包括click、mousedown、mouseup、keydown、keyup和keypress;mouseover和mouseout事件也冒泡,但处理它们并不容易,需要经常计算元素位置;focus、blur本身没有冒泡特性。