今天这篇文章我们开始详细讲解JS的事件。之前我们已经讲过许多事件,而我们今天要讲的主要是事件对象。之前我们学习的onclick事件用于得知一个元素是否被点击,但如果我们想知道一些更详细的信息,例如点击位置,点击键是左键还是右键,就需要用到event对象。又例如我们希望检测用户键盘按的到底是左键还是右键来完成相应的功能,也需要用到event对象。
event对象和事件冒泡
event对象的作用是帮助我们获取一些事件的信息,例如鼠标位置,键盘按键。这里我们用一个小例子来说明event对象是怎么运作的。现在我们试图实现一个效果:无论点击页面的什么地方,都可以弹窗。聪明的同学可能已经猜到,我们可以直接给body添加onclick事件来实现这一点:
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8" /> <title>无标题文档</title> <script>window.onload=function (){ document.body.onclick=function () { alert('a'); };}; </script> </head> <body> </body> </html>
但实际上你会发现,无论怎么点击页面都无法进行弹窗,这是为什么呢?现在我们对这个页面改造一下:
<html> <head> <meta charset="utf-8" /> <title>无标题文档</title> <script>window.onload=function (){ document.body.onclick=function () { alert('a'); };};</script> </head> <body > <input type="button"value="aaa"> </body> </html>
可以看到的是,在body内没有内容的时候,body页面是撑不起来的(至少在ie下是这样的),我们在页面中加入了一个button后,body的大小也随着button略微增大。当然我们可以从样式角度去解决这个问题,但是我们现在从程序的角度考虑的话,应该怎么去解决他呢?其实很简单,我们不选择给body添加onclick事件而是直接给document添加就可以了:
window.onload=function (){ document.onclick=function () { alert('a'); }; };
效果如下:
这里body也没有被撑起来,但是document代表的是整个网页,因此可以通过点击页面任何地方弹窗。这里我们通过一个例子来讲讲document到底是什么东西。
window.onload=function (){ alert(document.childNodes[0].tagName); };
效果如下:
这里本应该打印出document第一个子节点的标签名,但返回结果为undefined。顺便大家可以用IE7测试一下,其返回结果会为一个感叹号。聪明的同学应该可以猜到,感叹号其实就是DOCTYPE声明中的第一个字符,DOCTYPE声明本身也是作为一个节点存在的。
实际上我们可以这么理解:在一个页面里,DOCTYPE和html存在同一个父级,也就是document(一个看不见的虚拟节点),他包括了整个网页的内容和网页声明。我们提了这么多关于document的知识,为的就是告诉大家一点:如果想给整个页面添加事件,那么使用document一定比使用html好,因为html页面是有可能撑不开的。回到我们的事件对象来。我们通过event对象可以获取事件的信息,例如可以退获取点击的坐标:
window.onload=function (){ document.onclick=function (ev) { //IE //alert(event.clientX+','+event.clientY); //FF //alert(ev.clientX+','+ev.clientY); var oEvent=ev||event; alert(oEvent.clientX+','+oEvent.clientY); }; };
在ie浏览器下,直接通过event.clientX和event.clientY就可以获取点击的坐标,而在火狐下,event对象是不兼容的,需要给事件读入一个参数(参数本身就是事件对象,由系统读入),用这个参数来调用clientX和clientY属性获取坐标。而“var oEvent=ev||event;”这种写法,系统会自动获取为真的一边,这样就可以通过一句话完美解决兼容性的问题了。
说完事件对象后我们来看看事件流?事件流说的简单一点,其实就是事件跟水一样从头到尾流动的过程。我们来看一个关于事件流的最简单的小例子:
<!DOCTYPE HTML> <html onclick="alert('html');"> <head> <meta charset="utf-8"> <title>无标题文档</title> <style> div {padding:100px;} </style> </head> <body onclick="alert('body');"> <div onclick="alert(this.style.background);"> <div onclick="alert(this.style.background);"> <div onclick="alert(this.style.background);"> </div> </div> </div> </body> </html>
效果如下:
当我们点击最里面的一个div时,它会连续弹出多个框,原因大家应该都明白。这里,就涉及到了事件冒泡的知识——在这个程序里,当我们点击了最里面的div并执行事件后,还会将这个事件传递给父级继续执行,依次类推,直到html和document。这就是所谓的事件冒泡——事件会随着层级依次传递到底。
事件冒泡学完以后大家更多的疑问肯定是,我们能用它来做点什么,毕竟学了任何知识都是拿来用的,而不是看的。一个小小的结论是:在我们平时做东西的时候,真正主动去利用时间冒泡去做的事情非常少,甚至很多时候事件冒泡都会给我们带来烦恼脑。为什么这么说呢,下面我们来看一个小例子,立刻就能明白了。
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>无标题文档</title> <style> #div1 {width:400px; height:300px; background:#CCC; display:none;} </style> <script> window.onload=function () { var oBtn=document.getElementById('btn1'); var oDiv=document.getElementById('div1'); oBtn.onclick=function (ev) { var oEvent=ev||event; oDiv.style.display='block'; //alert('按钮被点击了'); oEvent.cancelBubble=true; }; document.onclick=function () { oDiv.style.display='none'; //alert('document被点击了'); }; }; </script> </head> <body> <input type="button" value="显示" /> <div > </div> </body> </html>
我们试想一下,我们想要实现一个功能:点击页面任意地方,都可以让已经显示的div块隐藏,根据我们前面所学的知识,最好的方法应该是给document添加事件。但如果我们直接这么做的话,就会存在下面这个问题:我们在点击按钮的时候,这个事件也会被传递到其根元素document上,也就是说,点击了按钮后,会触发两个事件,div依然会被隐藏。取消冒泡的方式也是通过事件对象,在event对象里有一个属性叫cancelBubble,当我们将它设置为true的时候,就可以取消该元素的事件冒泡了。
鼠标事件
下面我们来说一下鼠标事件,之前说过了,clientX,clinentY可以获取鼠标的坐标。不过,我们仅仅说它是鼠标的坐标,而并没有说它是鼠标的什么坐标。现在我们来看一下clientX,clinentY有一些小问题。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>无标题文档</title> <style> #div1 {width:200px; height:200px; background:red; position:absolute;} </style> <script> document.onmousemove=function (ev) { var oEvent=ev||event; var oDiv=document.getElementById('div1'); oDiv.style.left=oEvent.clientX+'px'; oDiv.style.top=oEvent.clientY+'px'; };</script> </head> <body> <div > </div> </body> </html>
效果如下:
我们让div1坐标就等于鼠标的当前坐标,可以看到我们实现了一个div随着鼠标移动的效果。但这个程序存在一个小小的隐患——例如,现在如果我们把body的高度设置为2000px,会发生什么呢?实际上,无论是clientX还是clientY,它们代表的实际含义都是可视区坐标,而div则是根据body来定位的,所以当可视区并不在页面顶端时,必然会出现鼠标和div之间的错位。这里,我们可以使用scrollTop来解决这个问题。
<html> <head> <meta charset="utf-8"> <title>无标题文档</title> <style> #div1 {width:200px; height:200px; background:red; position:absolute;} </style> <script> document.onmousemove=function (ev){ var oEvent=ev||event; var oDiv=document.getElementById('div1'); var scrollTop=document.documentElement.scrollTop||document.body.scrollTop; oDiv.style.left=oEvent.clientX+'px'; oDiv.style.top=oEvent.clientY+scrollTop+'px'; }; </script> </head> <body > <div ></div> </body> </html>
无论是否滚动页面,都不会出现错位的问题了(不过会出现div抖动的情况,我们以后会有方法解决)。一个小小的经验是:但凡是你用到clientX,clientY的时候一定要加上scrollLeft或者scrollTop,否则很容易出问题。
现在我们来看一下其另一个例子(这里我们将scroll封装成一个函数getPos。
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>无标题文档</title> <style> div {width:10px; height:10px; background:red; position:absolute;} </style> <script> function getPos(ev){ var scrollTop=document.documentElement.scrollTop||document.body.scrollTop; var scrollLeft=document.documentElement.scrollLeft||document.body.scrollLeft; return {x: ev.clientX+scrollLeft, y: ev.clientY+scrollTop};}document.onmousemove=function (ev){ var aDiv=document.getElementsByTagName('div'); var oEvent=ev||event; var pos=getPos(oEvent); for(var i=aDiv.length-1;i>0;i--) { aDiv[i].style.left=aDiv[i-1].offsetLeft+'px'; aDiv[i].style.top=aDiv[i-1].offsetTop+'px'; } aDiv[0].style.left=pos.x+'px'; aDiv[0].style.top=pos.y+'px'; }; </script> </head> <body><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div> </body> </html>
效果如下:
这里制作出了一串div跟随着鼠标走的特效。我们让后一个div都跟着前一个div走,第一个div跟着鼠标走,就可以实现这种效果了。
键盘事件
说完鼠标事件我们来看看键盘事件。通过keyCode,我们可以获取用户操作的是哪个按键。这里我们需要介绍两个新事件:onkeydown和onkeyup,分别代表按下键盘按键和放开键盘按键的事件。
这里通过一个小例子来说明他们是怎么用的:
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>无标题文档</title> <script> document.onkeydown=function (ev) { var oEvent=ev||event; alert(oEvent.keyCode); }; </script> </head> <body> </body> </html>
可以看到的是,我们输入一个键盘按键后,页面会弹出一个数字,实际上键盘几乎每个按键都有对应的keyCode,因此keyCode可以很好地为我们检测用户按的是哪个键。现在我们来看一个通过keyCode用键盘控制div的移动的小例子:
<html> <head> <meta charset="utf-8"> <title>无标题文档</title> <style> #div1 {width:100px; height:100px; background:#CCC; position:absolute;} </style> <script> document.onkeydown=function (ev) { var oEvent=ev||event; var oDiv=document.getElementById('div1'); if(oEvent.keyCode==37) { oDiv.style.left=oDiv.offsetLeft-10+'px'; } else if(oEvent.keyCode==39) { oDiv.style.left=oDiv.offsetLeft+10+'px'; } }; </script> </head> <body> <div ></div> </body> </html>
值得注意的一点是,如果我们按住一个方向键不动,可以看到div块会先定住一会儿,然后才开始移动(实际上我们平时打字的时候也是这样),这里给大家留一个悬念,大家可以自己思考一下如何解决这个问题。除了keyCode外,JS键盘事件还有几个属性:ctrlKey,altKey,shiftKey等。我们在论坛或微博经常可以看到ctrl+回车提交留言的功能,就是通过这些属性完成的:
<!DOCTYPE HTML><html> <head> <meta charset="utf-8"> <title>无标题文档</title> <script> window.onload=function () { var oTxt1=document.getElementById('txt1'); var oTxt2=document.getElementById('txt2'); oTxt1.onkeydown=function (ev) { var oEvent=ev||event; if(oEvent.keyCode==13 && oEvent.ctrlKey) { oTxt2.value+=oTxt1.value+'n'; oTxt1.value=''; } }; }; </script> </head> <body> <input type="text" /><br> <textarea rows="10" cols="40"></textarea> </body> </html>
oEvent.keyCode==13 && oEvent.ctrlKey代表的就是在按回车的同时按住ctrl键——我们就完成了一个ctrl+回车提交的功能。从下节课我们会讲一些事件更加复杂,更加高级一些的应用。