每当猴子们问我JavaScript和DOM里啥东西最牛逼时,我都会一巴掌打回去:卧槽还用问么固然是事件响应了啊!没它你能有时间和我讨论这个?你早去工地搬砖去了好么!浏览器没有事件响应就没有行为交互,那简直就是一晚上回到解放前的感受啊。此外,以事件驱动使得功能解耦也是个至关高端大气的技巧了,嘛,以此为主的Node.js 如今但是风生水起的说。 javascript
那如今咱们就再抠抠事件监听的相关基础,让你们在心情愉悦的状态下得到更多的姿♂势。不过那些常常写和在标签上写onclick=”foo()”的猴子们请自动回避,当心你看不懂又想不开,老衲徒增罪孽呀(偶八年前就解释了内联事件处理是自寻死路)。 css
再唠叨两句:本文的代码内容只涉及到原生JavaScript,像JQuery,YUI或Dojo什么的所提供的事件处理这里就不加以赘述了。我但愿你们可以明白,使用这些库只是为了方便,但咱们却不能彻底依赖它。了解基础与理解本质是很是重要的,这样你就能够在不能使用傻瓜库的状况下,依旧能够提供一个牛逼的解决方案。 html
进化主义声明:这里咱们使用的事件语法是“DOM Level 3 Events”规范定义的“addEventListener()。除了IE9如下版本之外的现代浏览器都支持。固然,咱们可使用JQuery,它会帮我解决这些浏览器兼容性的问题。但若是你还想让互联网能够良好发展和进化,你就应该马上中止为兼容低级浏览器而再去写一坨屎同样的傻逼兼容代码。这条路虽然艰辛,但却无比正确。能够试着给你的产品进行功能降级,检测到是低级浏览器就不执行脚本,好比addEventListener()的DOMContentLoaded事件就能确保你的代码不在低级浏览器中运行,而页面能够将主体内容正常显示就OK的。 html5
在咱们进入事件的细节以前先看几个牛逼的演示,它利用onscroll事件获得了一个很nice的效果: java
以上全部都是基于浏览器的事件监听和处理功能,因此,让咱们细细品味一下原汁原味的事件机制吧。 node
1
2
3
4
5
6
7
|
varlog = document.getElementById('log'),
i =
'',
out = [];
for(i
inwindow) {
if( /^on/.test(i)) { out[out.length] = i; }
}
log.innerHTML = out.join(', ');
|
在浏览器中运行如上代码,亲们能够获得以下: git
onmouseenter, onmouseleave, onafterprint, onbeforeprint, onbeforeunload, onhashchange, onmessage, onoffline, ononline, onpopstate, onpagehide, onpageshow, onresize, onunload, ondevicemotion, ondeviceorientation, onabort, onblur, oncanplay, oncanplaythrough, onchange, onclick, oncontextmenu, ondblclick, ondrag, ondragend, ondragenter, ondragleave, ondragover, ondragstart, ondrop, ondurationchange, onemptied, onended, onerror, onfocus, oninput, oninvalid, onkeydown, onkeypress, onkeyup, onload, onloadeddata, onloadedmetadata, onloadstart, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onmozfullscreenchange, onmozfullscreenerror, onpause, onplay, onplaying, onprogress, onratechange, onreset, onscroll, onseeked, onseeking, onselect, onshow, onstalled, onsubmit, onsuspend, ontimeupdate, onvolumechange, onwaiting, oncopy, oncut, onpaste, onbeforescriptexecute, onafterscriptexecute github
一大坨事件就够你吃几天的了,用addEventListener()方法能够进行事件监听: web
1
|
element.addEventListener(event, handler, useCapture);
|
举个例子来讲: ajax
1
2
|
vara = document.querySelector('a');
// grab the first link in the document
a.addEventListener('click', ajaxloader,
false);
|
咱们在一个element上加了个事件监听,就好像是在命令她,“你被客人摸了你就给我喊起来!” The ajaxloader()是监听事件的回调方法,就好像是,“你就在这儿给我盯着,妞要是喊了,你就过去给客人道歉!” 将第三个参数useCapture设置为false是为了表示此次是在事件冒泡阶段进行触发,而不是在事件捕获阶段。咳咳,这是一个漫长而艰巨的课题,你也能够看看Dev.Opera对capture的解释。哎呀反正不用管那么多啦,99.7434%的状况下设置useCapture为false准没错!其实它默认就是false,因此按理来讲是能够不用填写的,但Opera这逗比例外…
在事件被触发之时,回调方法会接收到一个事件对象。请试着在恰当的环境中运行以下代码,也能够直接点击这里测试这个例子,对象内的属性又够吃一盆的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
varlog = document.getElementById('log'),
out =
'';
document.addEventListener('click', logeventinfo,
false);
document.addEventListener('keypress', logeventinfo,
false);
functionlogeventinfo (ev) {
log.innerHTML =
'';
out =
'
for(vari
inev) {
if(typeofev[i] ===
'function'|| i === i.toUpperCase()) {
continue;
}
out +=
'
}
log.innerHTML += out +
'</ul>';
}
|
你能够对同一事件进行多重监听,也能够对多个事件使用同一方法处理(如本例)。
参数ev是传回来的事件对象,下面是它所带的所有属性:
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
|
originalTarget: [object HTMLHtmlElement]
type: click
target: [object HTMLHtmlElement]
currentTarget: [object HTMLDocument]
eventPhase: 3
bubbles:
true
cancelable:
true
timeStamp: 574553210
defaultPrevented:
false
which: 1
rangeParent: [object Text]
rangeOffset: 23
pageX: 182
pageY: 111
isChar:
false
screenX: 1016
screenY: 572
clientX: 182
clientY: 111
ctrlKey:
false
shiftKey:
false
altKey:
false
metaKey:
false
button: 0
relatedTarget:
null
mozPressure: 0
mozInputSource: 1
view: [object Window]
detail: 1
layerX: 182
layerY: 111
cancelBubble:
false
explicitOriginalTarget: [object HTMLHtmlElement]
isTrusted:
true
originalTarget: [object HTMLHeadingElement]
type: click
target: [object HTMLHeadingElement]
currentTarget: [object HTMLDocument]
eventPhase: 3
bubbles:
true
cancelable:
true
timeStamp: 574554192
defaultPrevented:
false
which: 1
rangeParent: [object Text]
rangeOffset: 0
pageX: 1
pageY: 18
isChar:
false
screenX: 835
screenY: 479
clientX: 1
clientY: 18
ctrlKey:
false
shiftKey:
false
altKey:
false
metaKey:
false
button: 0
relatedTarget:
null
mozPressure: 0
mozInputSource: 1
view: [object Window]
detail: 1
layerX: 1
layerY: 18
cancelBubble:
false
explicitOriginalTarget: [object Text]
isTrusted:
true
|
试一下本例中点击鼠标和按键盘,不一样的事件触发传回来的结果是不一样的。能够看看完整的标准事件属性列表。
咱们须要了解浏览器中关于事件对象有两个很重要的功能。阻止浏览器执行事件默认行为的ev.preventDefault(),和能够得到事件目标元素的ev.target.
好比说一个连接被点击了,但由于业务须要,咱们并不想让浏览器打开新页面。这时候能够给这个连接加个点击事件监听,而后在回调函数中调用 preventDefault()方法便可。昂,就以下面这个例子,请看HTML:
还有JavaScript:
1
2
3
4
5
6
7
8
9
10
11
|
varnormal = document.querySelector('.normal'),
prevent = document.querySelector('.prevent');
prevent.addEventListener('click',
function(ev) {
alert('fabulous, really!');
ev.preventDefault();
},
false);
normal.addEventListener('click',
function(ev) {
alert('fabulous, really!');
},
false);
|
注意: document.querySelector() 是合理获取DOM元素的一种方式。和jQuery的 $() 差很少。 能够读读 W3C’s specification 和MDN的 explanatory code snippets 去了解。
若是点击.prevent连接,会弹出个对话框,点“肯定”后啥事都没发生,呵~呵~,由于处理中有执行ev.preventDefault(),因此不会跳到 http://smashingmagazine.com。没有 preventDefault()的就会在弹对话框,且跳连接咯。不信你能够试一下嘛。
一般状况下,处理事件的方法想要访问触发元素只能经过变量和this什么的去关联,虽然看似简单方便,但addEventListener()给了咱们更好的选择:事件目标(target),不过它可能被其余的一些东西混淆,因此用ev.currentTarget更保险些。一般想要在点击、悬停或输入事件的回调方法中访问触发元素都是用变量 this 。虽然方便,但这个关键字你懂得…因而 addEventListener() 提供给咱们一个更好的获取方式:event.target。 不过它可能会被混淆( this 被绑到奇怪的东西上的时候),因此用 ev.currentTarget 更保险些。
用事件对象的 target 属性,你能够获得触发事件的元素。
事件被激活后,会像猴子同样沿着DOM树从监听节点下滑到触发节点,而后再上爬回监听节点。也就是说,若是你监听了一个DOM节点,那也就等于你监听了其全部的后代节点。代理的意思就是只监听父节点的事件触发,以来代理对其后代节点的监听,而你须要作的只是经过 target 属性获得触发元素并做出回应。来看我下面的例子:
1
2
3
4
5
6
7
|
</ul>
|
这个例子中的HTML结构是个无序列表,当鼠标悬停会显示相应的标签信息。下面是它JS代码,你将看到它只用到了一个事件监听,而后在处理函数中获得target,以它的 tagName 来进行区分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
varresources = document.querySelector('#resources'),
log = document.querySelector('#log');
resources.addEventListener('mouseover', showtarget,
false);
functionshowtarget(ev) {
vartarget = ev.target;
if(target.tagName ===
'A') {
log.innerHTML =
'A link, with the href:'+ target.href;
}
if(target.tagName ===
'LI') {
log.innerHTML =
'A list item';
}
if(target.tagName ===
'UL') {
log.innerHTML =
'The list itself';
}
}
|
我费事吧啦解释了半天,乃们懂得这么作意味着什么么?这意味着你能够节省大量重复的事件监听,以减小浏览器资源消耗。大多数人可能会用jQuery的$(‘a’).click(…) 啥啥啥的(虽然 jQuery的 on 方法优化的还OK啦不过偶仍是蛮鄙视他的),这么作看似一句话蛮带感的,可其实它是把获取到的全部A元素一个一个的注册监听!而后在某个时刻,充斥着无数事件监听的页面终于觉累不爱,自爆以鸣冤屈。
她还有一个好处就是让HTML独立起来,好比以后还有要加子元素的需求,也不须要再为其单独加事件监听了。
之前咱们会用mouseover|mouseout事件来暗挫挫的实现hover效果,而如今用CSS伪选择器的:hover和:focus什么的就直接搞定了。想到这里,心里止不住的伤感啊……魔法师们坚持住!咳咳,固然,CSS也并非万能的,有些事情仍是要跟事件配合完成,好比下面这个例子,对鼠标指针进行定位。这是至关简单的了是不,咱们先搞个绝对定位的小球元素,下面是它的HTML:
1
|
"plot"<
|
这是它的CSS:
1
2
3
4
5
6
7
8
9
10
|
.plot {
position:absolute;
background:rgb(175,50,50);
width: 20px;
height: 20px;
border-radius: 20px;
display: block;
top:0;
left:0;
}
|
咱们监听并处理doucment的click事件,利用PageX和pageY对小球进行定位。注意啊这里,咱们须要减去球的半径,以让球的中心在鼠标指针上:
1
2
3
4
5
6
|
varplot = document.querySelector('.plot'),
offset = plot.offsetWidth / 2;
document.addEventListener('click',
function(ev) {
plot.style.left = (ev.pageX - offset) +
'px';
plot.style.top = (ev.pageY - offset) +
'px';
},
false);
|
随便点击屏幕的任意位置,小球都会随之闪现到那。不过它并非平滑过去的,但若是你勾选这个示例的复选框,你会发现小球就会很圆润的滑过来了。这个效果呢,过去的话可能只能用JS库来完成,但如今啊,时代不一样了……咱们只须要用CSS写个过渡效果的类,而剩下的事情就让浏览器去处理。为至于此,咱们写个类名为smooth的样式,在复选框被选中以后,将其应用到小球上:
1
2
3
4
5
6
7
|
.smooth {
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
-ms-transition: 0.5s;
-o-transition: 0.5s;
transition: 0.5s;
}
|
添加JavaScript:
1
2
3
4
|
varcb = document.querySelector('input[type=checkbox]');
cb.addEventListener('click',
function(ev) {
plot.classList.toggle('smooth');
},
false);
|
随着新世界的来临,CSS和JavaScript双剑合璧,谁与争锋!嘛顺便说下,在JS中也有跟CSS过渡和动画效果有关的事件噢。
正如你以前在可用事件列表中看到的,咱们也能够监听按键输入事件。不过很遗憾的是,浏览器对键盘事件处理的并非很到位,你能够看看Jan Wolter对此的详细解释接下来让咱们看一个keytime的例子,它会输出用户按键的毫秒间隔。代码并不难:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
varresources = document.querySelector('#resources'),
log = document.querySelector('#log'),
time = 0;
document.addEventListener('keydown', keydown,
false);
document.addEventListener('keyup', keyup,
false);
functionkeydown(ev) {
if(time === 0) {
time = ev.timeStamp;
log.classList.add('animate');
}
}
functionkeyup(ev) {
if(time !== 0) {
log.innerHTML = ev.timeStamp - time;
time = 0;
log.classList.remove('animate');
}
}
|
先定义咱们想要操纵的元素并设置time为0。而后咱们在document上监听两个键盘输入事件 keydown和keyup。
在keydown事件处理中,咱们检查变量time是否为0,若是是则把事件对象的timeStamp赋值给time。再加个CSS动画类animate给log节点,让它向滚动条同样动起来
在keyup事件处理中,若是time仍是为0则忽略(在按着键盘的期间keydown事件是接二连三被触发的),若是不是则经过二者时间戳相减去计算一次按键操做通过多长时间。最后让time为0并移除log节点的animate类
当浏览器运行CSS过渡效果是会在JavaScript中触发一个独立事件,叫transitionend。这个事件对象会有两个属性:被其所影响到的属性名propertyName,和其过渡所经历的时间elapsedTime。
能够查看这个demo感觉一下,代码很简单,下面是它的CSS:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
.plot {
background:rgb(175,50,50);
width: 20px;
height: 20px;
border-radius: 20px;
display: block;
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
-ms-transition: 0.5s;
-o-transition: 0.5s;
transition: 0.5s;
}
.plot:hover {
width: 50px;
height: 50px;
border-radius: 100px;
background: blue;
}
|
这是它的JavaScript:
1
2
3
|
plot.addEventListener('transitionend',
function(ev) {
log.innerHTML += ev.propertyName +
':'+ ev.elapsedTime +
's ';
},
false);
|
但由于Fire/Chrome/Safari/Opera等这些浏览器厂商各自为政,也是由于这些事件还不成熟,因此这些事件名一般都会被加上前缀,那在使用时你就不得不判断下浏览器兼容性。能够看看这个David Calhoun’s gist。
CSS动画事件和上面演示的过渡事件基本一个意思,它有三个事件:animationstart,animationend和animationiteration。能够看MDN的demo。
事件咱们是监听到了,但若是想让它更加屌炸天,咱们就须要再来点有深度的,好比在用户在拖拽元素时,给元素来个计算角度、距离和速度差什么的——示例。
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
|
varplot = document.querySelector('.plot'),
log = document.querySelector('output'),
offset = plot.offsetWidth / 2,
pressed =
false,
start = 0, x = 0, y = 0, end = 0, ex = 0, ey = 0, mx = 0, my = 0,
duration = 0, dist = 0, angle = 0;
document.addEventListener('mousedown', onmousedown,
false);
document.addEventListener('mouseup', onmouseup,
false);
document.addEventListener('mousemove', onmousemove,
false);
functiononmousedown(ev) {
if(start === 0 && x === 0 && y === 0) {
start = ev.timeStamp;
x = ev.clientX;
y = ev.clientY;
moveplot(x, y);
pressed =
true;
}
}
functiononmouseup(ev) {
end = ev.timeStamp;
duration = end - start;
ex = ev.clientX;
ey = ev.clientY;
mx = ex - x;
my = ey - y;
dist = Math.sqrt(mx * mx + my * my);
start = x = y = 0;
pressed =
false;
angle = Math.atan2( my, mx ) * 180 / Math.PI;
log.innerHTML =
'
'
+ (dist<<0) +'</strong> pixels in '+
duration +'</strong> ms ( '+
twofloat(dist/duration) +'</strong> pixels/ms)'+
' at '+ twofloat(angle) +
'</strong> degrees';
}
functiononmousemove (ev) {
if(pressed) {
moveplot(ev.pageX, ev.pageY);
}
}
functiontwofloat(val) {
returnMath.round((val*100))/100;
}
functionmoveplot(x, y) {
plot.style.left = (x - offset) + 'px';
plot.style.top = (y - offset) + 'px';
}
|
好啦,好像作了不少事情的样子,但事实上并无那么复杂。监听onmousedown和onmouseup两个事件,咱们能获得鼠标当前位置clientX和clientY,还有记录按键时间的timeStamp。当鼠标移动时,检查鼠标是否被按下了(经过在mousedown时设定的布尔值),若是按下了则让小球跟着鼠标移动。
而后是几何——Pythagoras(毕达哥拉斯定理)经过mousedown和mouseup的时间间隔和像素位移而得出它运动的速度。
咱们获得了运动开始和结束的xy坐标,相减获得距离差,再平方相加,最后获得和的平方根,即为小球运动的位移。嘛咱们还经过计算三角形的反正切获得了它运动先后的偏转角度,高端吧!嘿嘿其实这些都是抄“A Quick Look Into the Math of Animations With JavaScript”的……你能够看下这个在JSFiddle上的示例:
http://jsfiddle.net/codepo8/bAwUf/light/
video和audio这俩很潮的玩意也有一大堆事件供咱们使用。好比有趣的time事件,它能够告诉咱们这首歌或电影的已播放时长。能够看看MDN这个MGM-inspired dinosaur animation;我也没事闲的录制了一个six-minute screencast来玩弄了一下~。
想看全部的事件动做?去JPlayer看媒体事件的演示页面吧。
咱们知道,浏览器提供了与鼠标键盘的交互,但这还远远不够知足咱们更多的硬件交互需求。好比检测手机或平板电脑倾斜度的Device orientation和 touch events。the Gamepad API让咱们能够在浏览器中作游戏控制;postMessage让咱们能够在浏览器各窗口之间进行跨域消息传递;pageVisibility让咱们能够得知浏览器中当前标签页可见状态。甚至当window的history对象有操做时也能监听的到。查看window对象的事件列表,有的可能已经被实现了,还有更多的在谋划中……
嘛,无论浏览器是否会支持,最终都是要支持的嘛,这些是刚需。咱们只要默默等待就能够了,骚年,向着夕阳奔跑吧!=v=
基本上就这样了,看,事件并不难。通常状况下,你只须要注册监听他们,而后在事件处理函数中使用event对象就行了。若是到如今你还没想到它能作些什么有趣的事情,那这篇文章也只能帮你倒这里了。别再管上文的那些例子了,请你用你那上锈的脑壳好好思考下,玩愤怒小鸟时候,不就是监听个触摸事件的开始与结束,再处理下相应的方向和距离差所得出的射击力量么?最后嗖的一声,小鸟就自由飞翔在远方了~。因此,到底是什么阻止了你的创意?该加了个油了,同窗。