P111. 事件对象
概念:
当事件的响应函数触发时,浏览器都会将一个事件对象作为实参传入响应函数,该事件对象封装了当前事件相关的一切信息,如:鼠标的坐标,键盘的哪个键被按下….
可参考网页:https://www.w3school.com.cn/jsref/dom_obj_event.asp
注意:事件对象在IE8及以下版本的浏览器时作为window的属性,即作为全局变量存在的,而不是传参 但是有些浏览器又不作为全局变量传参,这时候我们需要做一个简单的判断:
btn.onclick = function(){//我们用一个简单的或运算,如果event存在就接着保持,为false就为window.eventevent = event || window.event;var x = parseInt(event.clientX);var y = parseInt(event.clientY);};
知识拓展,更多向前参考上方网页
onmousemove
P113. 事件的冒泡
所谓事件的冒泡,就是指事件的向上传导,当后代上的事件被触发时,其父元素、祖先元素上的相同事件也会被触发。
如:我们在body内写了一个div,div内再写一个div,其中写了一个span标签,此时我们为这三个元素节点都添加onclick事件。那么当span标签onclick内的单击响应函数被触发时,其父元素的两个div单击响应函数也会被触发,甚至如果html设置了单击响应函数,还会被触发html设置了单击响应函数,还会被触发。
...<script type="text/javascript">window.onload = function(){var span = document.querySelector("#s1");var d1 = document.querySelector("#d1");var d2 = document.querySelector("#d2");s1.onclick = function(){alert("one");}d1.onclick = function(){alert("three");}d2.onclick = function(){alert("two");}}; //按上方字母顺序触发,one->two->three。</script>...<body><div style="background-color: antiquewhite;" id="d1"><div style="background-color: aqua;" id="d2"><span style="background-color: azure;" id="s1">abc</span><!-- 此时,如果我们点击这个span标签,其本身会最先触发单击响应函数,其次d2,最后d1 --></div></div></body>
在开发中,事件的冒泡在大部分情况下都是有益的,但是如果不希望发生事件的冒泡可以通过事件对象来取消冒泡。
cancelBubble
我们可以将这个属性设置为true,那么就取消了事件的冒泡属性。
使用方法如:
...var a = document.querySelector("#a");a.onmousemove = function(event){event = event || window.event;event.cancelBubble = true; //此时a标签的父元素不会收到这个函数的影响}...
P114. 事件的委派
引入 —— 单击按钮添加超链接
...<script type="text/javascript">window.onload = function(){var allA = document.getElementsByTagName("a");var btn = document.querySelector("#btn");var ul = document.querySelector("#u1");//添加超链接btn.onclick = function(){var li = document.createElement("li");li.innerHTML = "<a href='javascript:;'>新建超链接</a>";ul.appendChild(li);};//为每一个超链接添加单机响应函数for (var i = 0; i<allA.length; i++){allA[i].onclick = function(){ //每次都需要迭代,过于低效alert("这是a的单击响应函数!!");};}};</script>...<body><button id="btn">添加超链接</button><ul id="u1"><li><a href="javascript:;">超链接1</a></li><li><a href="javascript:;">超链接2</a></li><li><a href="javascript:;">超链接3</a></li></ul></body>...
缺陷:上面的代码还有个问题,添加新的超链接无法自动添加单击响应函数,因为我们是使用的循环的方式添加的单击响应函数,而循环在js代码执行完之后就不会再调用了。由此,我们不推荐使用循环的方法定义响应函数。
改进
我们希望只绑定一次事件,就可以应用到多个元素上,即使元素是后添加的,这种方法简单高效。
我们可以使用上节课学到的事件冒泡,给ul添加单击响应函数,ul是li的父元素,li是ul的后代,所以li可以享受到ul的函数 ,并且我们只绑定了一次,之后新增加超链接也会有这个函数。
//将for循环部分改为:ul.onclick = function(){alert("这是ul的超链接");}
委派的概念
- 指将事件统一绑定给元素共同的祖先元素,这样当后代元素上的事件被触发时,会一直冒泡到到祖先元素,从而通过祖先元素的响应函数来处理事件
- 事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能。
-
缺陷:
虽然这样可以使ul内的li元素都可以享受到响应函数,但是我们会发现,除了li标签之外的部分,也会收到响应函数的影响。
解决方式:
这时候我们可以给我们的函数添加event形参(参考前几节课笔记),其中event有一个target属性
- target — 返回触发事件的元素(事件的目标节点) -> 你点谁target返回的对象就是谁
我们可以给我们的li标签添加一个class标签,如:
...<ul id="u1"><li><a href="javascript:;" class="items">超链接1</a></li><li><a href="javascript:;" class="items">超链接2</a></li><li><a href="javascript:;" class="items">超链接3</a></li></ul>...
当我们target对象的class标签等于items的时候,才触发我们想要的效果
- 我们可以把刚刚的ul单击响应函数修改为:
//之后修改的超链接都绑定items这个类即可li.innerHTML = "<a href='javascript:;' class='items'>新建超链接</a>";...ul.onclick = function(event){event = event || window.event;if (event.target.className == "items"){ //如果你是a标签就执行,不然就不执行alert("我是a的单击响应函数");}}...
但是还剩一个隐患,class标签我们可以写多个,当我们<…class=”itms hello”>指定两个类的时候这种方法就不好使了,那我们可以怎么解决呢?可以先自己想一想…
P115. 事件的绑定
假设我们有一个需求,为一个对象绑定多个单击响应函数。
window.onload = function(){//实现点击按钮弹出一个内容var btn = document.querySelector("#btn");//为btn绑定第一个单击响应函数btn.onclick = function(){alert("one");};//为btn绑定第二个单击响应函数,实际只有这个函数起效果。btn.onclick = function(){alert("two");};};
我们会发现使用对象.事件 = 函数的方式好像不行,它只能为一个元素的一个事件绑定一个响应函数,不能绑定多个,如果绑定多个,后面的函数会覆盖前面的函数。
但是有些时候我们就是需要多个响应函数,我们可以使用下面的方法
addEventListener()
- 通过这个方法也可以为元素绑定响应函数,并且支持绑定多个相同事件的响应函数。
- 参数
- 事件的字符串,如:onclick… 但是注意,在这里不要写on。
- 回调函数,当事件触发时,该函数会被调用。
- 是否在捕获阶段触发事件,需要一个布尔值,一般传false。这点后面细讲…
使用方法如下:
//为btn绑定第二、第三个单击响应函数btn.addEventListener("click",function(){alert("two");},false);btn.addEventListener("click",function(){alert("three");},false);
使用addEventListener()可以同时为一个元素的相同事件绑定多个函数,当事件被触发时,函数会按照声明顺序依次执行
- 注意:这个方法在IE8及以下版本的浏览器不兼容!
attachEvent()
- 此方法在IE8中也可以使用,作用和addEventListener()相同。
- 参数
- 事件的字符串,要on
- 回调函数
使用方法如下:
btn.attachEvent("onclick",function(){alert("one");});btn.attachEvent("onclick",function(){alert("two");});
这个方法和上面的不同的是,这个方法是从下往上执行,所以这里会先输出two再输出one。
- 注意:这个方法在其他浏览器无效!
兼容问题
由于上方的两个事件不互相兼容,那我们可以定义一个函数解决这个问题。
需要解决的问题:
- addEventListener()中的this,是绑定事件的对象。
- attachEvent()中的this,是window。
- 需要统一两个方法的this。
- 解决不管在IE8还是其他浏览器都可以添加多个相同事件的响应函数。
参数:
- obj - 要绑定事件的对象
- eventStr - 事件的字符串
...window.onload = function(){var btn = document.querySelector("#btn");function bind(obj,eventStr,callback){if (obj.addEventListener) { //进行判断obj.addEventListener(eventStr,callback,false);}else {obj.attachEvent("on" + eventStr,callback);}}bind(btn,"click",function(){ //将按钮对象作为第一个参数,事件作为第二个参数alert("a");});bind(btn,"click",function(){alert("b");});};...
但是还遗留下了一个问题,那就是这样并没有统一它们的this参数。在火狐、谷歌等主流浏览器中与this是button,而IE8是window。
那我们怎么解决这个问题呢?
这就需要用到前面的学到的call方法(P79. 函数的方法),这个方法可以改变我们函数的this。
...function bind(obj,eventStr,callback){if (obj.addEventListener) {obj.addEventListener(eventStr,callback,false);} //上面的部分不变else {obj.attachEvent("on" + eventStr,function(){callback.call(obj); // 这里不再直接调用回调函数}); // 而是在回调函数中利用call方法改变this为obj对象}}bind(btn,"click",function(){ //此时如果输出this,我们会发现在任何浏览器都是输出button对象alert(this);});...
P117. 事件的传播
制作一个简单的网页。
// Css部分...<style type="text/css">#box1{width: 300px;height: 300px;background-color: aqua;}#box2{width: 200px;height: 200px;background-color: azure;}#box3{width: 100px;height: 100px;background-color: bisque;}</style>// Js部分<script type="text/javascript">window.onload = function(){var box1 = document.querySelector("#box1");var box2 = document.querySelector("#box2");var box3 = document.querySelector("#box3");box1.onclick = function(event){alert("box1");}box2.onclick = function(event){alert("box2");}box3.onclick = function(){alert("box3");}};</script><!-- HTML部分 -->...<body><div id="box1"><div id="box2"><div id="box3"></div></div></div></body>...
制作完成的网页呈现这样的效果。当我们点击其中的div会发现,函数会由内向外触发,也就是冒泡的概念,但是同时也涉及到了事件的传播。
事件的传播
关于事件的传播网景公司和微软公司有不同的概念 ->
- 微软公司认为事件的传播是由内向外传播。当事件触发时,应该先触发当前元素事件,再依次向祖先元素冒泡。
- 网景公司则认为事件的传播是由外向内传播。当事件触发时,应该先触发最外层祖先元素事件,再依次传播到当前元素事件。
W3C综合两家公司观念,将事件的传播分为3个阶段:
1. 捕获阶段:
- 在捕获阶段获取页面最外层祖先元素的事件,依次向当前元素传到,但只捕获,不触发事件。
2. 目标阶段:
- 事件捕获到当前目标元素,开始执行当前目标元素事件。
3. 冒泡阶段:
- 事件向其祖先元素传递,依次触发祖先元素事件。
但是如果我们需要在事件的捕获阶段就触发函数,也就是由外向内执行函数,我们将accEventListener()的第三个参数改为true即可实现!
将刚刚的单击响应函数改为:
box1.addEventListener("click",function(){alert("box1");},true);box2.addEventListener("click",function(){alert("box2");},true);box3.addEventListener("click",function(){alert("box3");},true); //这里的false改为true
但是我们一般不这么使用,我们大致了解即可。
P118. 拖拽练习一
P118 — P120三节课将制作一个拖拽页面元素的练习,下面是要求。
要求:
- 拖拽box1元素:
- 流程:
- 当鼠标拖拽时鼠标按下 -> onmousedown事件
- 当鼠标移动时box跟随鼠标移动 -> onmousemove事件
- 当鼠标松开时放下元素 -> onmouseup事件
初步版
- 当鼠标松开时放下元素 -> onmouseup事件
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style type="text/css">#box1{width: 100px;height: 100px;background-color: aqua;position:absolute;}</style><script type="text/javascript">window.onload = function(){var box1 = document.querySelector("#box1");var html = document.documentElement;box1.onmousedown = function(event){//ol和ot是鼠标指针距离box1边缘的位置var ol = event.clientX - box1.offsetLeft;var ot = event.clientY - box1.offsetTop;document.onmousemove = function(event){event = event || window.event;//这里的鼠标指针需要减去距离边缘的距离,防止鼠标指针始终在左上角。var left = event.clientX - ol;var top = event.clientY - ot;box1.style.left = left + "px";box1.style.top = top + "px";}//这是松开鼠标的事件,为了避免页面其他元素干扰,所以我们设置成docuemnt对象的事件document.onmouseup = function(){document.onmousemove = null; //松开鼠标后停止移动,固将移动事件设为nulldocument.onmouseup = null; //同时自身也要停止事件}}};</script></head><body><div id="box1"></div></body></html>
上面的代码已经解决了大部分需求,可以还是有一些小问题:
由于浏览器有个默认的事件,就是你选中元素拖拽可以快捷拖到搜索引擎进行搜索,可以我们会发现,当前代码下,一旦用户不小心选中到其他元素一起拖拽时,会影响我们的现有的拖拽方法。
下一节课将解决这个问题,同时会更加完善我们的代码。
P120. 拖拽练习二
要解决上面遗留下来的问题,首先的引入两个知识点。
在常规浏览器下解决问题:
还记得我们前面是怎么解决浏览器的默认事件的吗?如取消超链接默认事件。没错,我们是对函数返回fasle取消默认事件,所以在这里也是同样的道理,我们只需要在box1的onmousedown事件的最后,返回false即可。
在IE8以下版本的浏览器解决问题:
这就需要两个方法来进行操作:
- setCapture()
当调用此方法后,这个方法会把下一次所有的鼠标按下相关事件捕获到调用此方法的元素上。
注意:这个方法非常霸道,如:页面设置多个单击响应函数,我们将此事件绑定到btn1这个按钮上,那么不 管btn2还是点击页面中其他元素,都会触发btn1中的单击响应函数。甚至当我们点击桌面时,都会触发btn1的单击响应函数!
- 语法:元素节点.setCapture();
- releaseCapture()
当调用此方法后,会取消元素节点对于事件的捕获。使用setCapture后可以在适当的位置取消捕获,使程序更完善。
注意:这两个方法对于IE以外的浏览器可能不管用!
现在我们就可以实现代码了:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style type="text/css">#box1{width: 100px;height: 100px;background-color: aqua;position:absolute;}#box2{width: 100px;height: 100px;background-color:antiquewhite;position:absolute;left: 300px;top:300px;}</style><script type="text/javascript">window.onload = function(){var box1 = document.querySelector("#box1");var box2 = document.querySelector("#box2");//此方法我们可以封装为函数,那么我们想拖拽谁传谁就好。function drag(obj){obj.onmousedown = function(event){//由于一般浏览器不支持此方法,所有我们可以用&&操作符进行判断。obj.setCapture && obj.setCapture();event = event || window.event;var ol = event.clientX - obj.offsetLeft;var ot = event.clientY - obj.offsetTop;document.onmousemove = function(event){event = event || window.event;var left = event.clientX - ol;var top = event.clientY - ot;obj.style.left = left + "px";obj.style.top = top + "px";}document.onmouseup = function(){document.onmousemove = null;document.onmouseup = null;//和setCapture同理,当我们放下鼠标时,就应该停止捕获了。obj.releaseCapture && obj.releaseCapture();}return false;}}//调用函数drag(box1);drag(box2);};</script></head><body><div id="box1"></div><div id="box2"></div></body></html>
至此,我们拖拽练习就大致完成了,在实际应用中,我们还可以在里面加入图片或者其他标签,让程序更加灵活。当然,现在程序还有可以优化的地方,有实力的小伙伴可以继续研究一下!
P121. 鼠标滚轮事件
本节课将实现鼠标滚动向上滚时,box变短,滚轮向下滚时,box变长。
- onmousewheel
鼠标滚轮滚动事件,会在鼠标滚动时触发,但是火狐不兼容此属性。
- DOMMouseScroll
在火狐中需要使用此事件来绑定滚轮事件,注意,此属性需要利用addEventListener()函数来绑定,写在括号内字符串参数的位置。
判断鼠标滚动方向
event.wheelDelta
此属性可以获取鼠标的滚轮方向,当你向上滚动时,返回一个正数,向下滚动时,返回一个负数。
- 返回值的大小取决于鼠标滑动幅度
- 此属性在火狐浏览器不支持!
event.detail
- 此属性为火狐支持的判断滚轮方向的属性
- 向上滚动时返回负数,向下滚动时返回正数,与其他浏览器相反。
取消鼠标滚轮的默认事件
当滚轮滚动时,如果浏览器有滚动条,滚动条也会随之滚动,这是浏览器的默认行为,如果不希望发生,则需要取消默认行为:return fasle;即可。
可是此处火狐不支持用false取消默认事件,因为滚动事件是由addEventListener()添加的,使用addEventListener()添加的响应函数,不能返回false取消默认事件。
这时就需要使用event来取消默认行为,如:event.preventDefault();即可。
但是这个方法IE8又不支持!,如果直接调用会报错,我们可以利用以前所说的&&来解决问题,如:event.preventDefault && event.preventDefault();,如果浏览器有preventDefault就直接调用,没有就不调用。好的,现在就可以完成我们的练习了,下方是全部代码:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style type="text/css">#box1{width: 100px;height: 100px;background-color: red;}</style><script type="text/javascript">window.onload = function(){var box1 = document.querySelector("#box1");//滚轮事件box1.onmousewheel = function(event){event = event || window.event;//wheelDelta>0为普通浏览器向上滚,event.detail<0为火狐浏览器向上滚if (event.wheelDelta > 0 || event.detail < 0){//判断,往上滚最小不能小于10pxbox1.style.height = box1.clientHeight >= 60 ? box1.clientHeight - 50 + "px" : 10 + "px";}else{//往下滚边长box1.style.height = box1.clientHeight + 50 + "px";}//取消浏览器默认行为event.preventDefault && event.preventDefault();return false;}//利用前几节课学到bind函数,回调函数设置为box1的滚轮事件。bind(box1,"DOMMouseScroll",box1.onmousewheel);function bind(obj,str,callback){if (obj.addEventListener){obj.addEventListener(str,callback,false);}else{obj.attachEvent("on"+str,function(){callback.call(obj);});}}}</script></head><body style="height:2000px;"><div id="box1"></div></body></html>
P122. 键盘事件
键盘事件一般都不绑定给div这类标签,而是赋给那些能获取焦点的元素,如表单的text文本框…
onkeydown
- 某个键盘按键被按下。
对于onkeydown来说,如果你一直按着某个按键不松手,则事件会一直触发。
-
keyCode
判断键盘中哪个键被按下,并返回它的ASCII码值。
- 这是event对象的一个属性,要调用它不要给响应函数添加event。
- 使用方法如:
if (event.keyCode == 89) {执行语句...};,这句话表示按下键盘y键是执行某些语句。
但是如果我们想要判断Ctrl和y键是否同时被按下呢?请看下方例子。
document.onkeyup = function(event){if (event.keyCode == 89 && event.keyCode == 17 ){ //17为按下Ctrl是的返回值。alert('^y被按下');}}
这正确吗?这是一个典型的错误代码,千万不可以犯这个错误,一个值有可能即等于89,又等于17吗?
那么应该怎么做呢,其实事件对象中除了keyCode还提供了几个按键属性。
altKey、ctrlKey、shiftKey
- 分别判断alt键、ctrl键、shift键是否被按下
- 如果被按下返回true,反之返回false。
所以上面的代码可以改为:
document.onkeyup = function(event){if (event.keyCode == 89 && event.ctrlKey ){ //直接获取ctrl事件。alert('^y被按下');}}
我们可以将键盘事件配合表单文本框使用,达到一些巧妙的配合,但是有没有想过,如果文本框绑定的键盘响应函数返回一个false会是什么结果?如:
...<script type="text/javascript">window.onload = function(){var input = document.querySelector("input");input.onkeydown = function(){console.log("按下");return false; //这里取消输入的默认事件。}}</script>...<body><input type="text" id="txt"/></body>...
这里的默认事件是什么呢,文本框默认肯定是允许你输入值的呗,如果你取消了默认事件,那么用户输入了值,但是文本框内却不会输出结果了。
如果想做一个程序,让用户不能输入数字怎么办呢?
input.onkeydown = function(event){if (event.keyCode >= 48 && event.keyCode <= 57){ //数字的区间在48到57之间。return false;}}
但是这个方法有个Bug,就是如果你在中文状态下输入数字,或者使用小键盘输入数字。这时因为中文状态下的键盘和小键盘返回的编码不同,如果有这个需求还需要进一步适配,这里仅做示范。
P123. 键盘移动div
有了以上的知识,我们就可以做一个小程序了,我们可以尝试一个按方向键移动box。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style type="text/css">#box1{width: 100px;height: 100px;background-color: red;position: absolute;}</style><script type="text/javascript">window.onload = function(){var speed = 20; //定义初始熟读document.onkeydown = function(event){//上:38 下:40 左:37 右:39if (event.ctrlKey){speed += 20; //按ctrl加速。}var box1 = document.querySelector("#box1");//判断方向并移动switch(event.keyCode){case 38:box1.style.top = box1.offsetTop - speed + "px";break;case 40:box1.style.top = box1.offsetTop + speed + "px";break;case 37:box1.style.left = box1.offsetLeft - speed + "px";break;case 39:box1.style.left = box1.offsetLeft + speed + "px";break;}}}</script></head><body><div id="box1"></div></body></html>
现在还是有一些缺陷,那就是第一次移动时会有停顿感,这在稍微大一些的游戏中可能是致命的,不管以现在的知识还无法解决这个问题,后面的课我们会解决这个问题。有兴趣的朋友可以自己尝试解决。
