事件:
1. 事件绑定: 3种:
(1). 在HTML中绑定:
a. html中: <元素 on事件名=“事件处理函数()”>
b. js中: function 事件处理函数(){
…
}
c. 问题: 不符合行为(js)和内容(html)分离的原则,不便于维护
d. 但是: 在Vue,react等框架的开发中,这种绑定方式又回来了!(待续…)
(2). 在js中用赋值方式:
a. 元素对象.on事件名=function(){
… …
}
b. 问题: 如果一个事件上可能同时绑定多个事件处理函数时,使用赋值方式绑定,多个事件处理函数无法并存在一个事件属性行。只有最后赋值的一个事件处理函数才能留在元素上!覆盖之前所有赋值的事件处理函数
(3). 在js中用添加事件监听对象的方式:
a. 元素对象.addEventListener(“事件名”,事件处理函数)
添加事件监听对象
b. 原理:
(1). 其实除了元素对象身上的事件属性上可以保存事件处理函数之外,浏览器内存中还有一个巨大的事件监听对象队列,也可以保存事件监听对象
(2). addEventListener做2件事:
a. 创建一个事件监听对象保存三个信息:
1). 哪个元素
2). 什么事件名
3). 处理函数是什么
b. 然后将事件监听对象添加到浏览器的事件监听对象队列中保存
(3). 当一个元素上发生事件时:
a. 浏览器先查找元素对象自身的on事件属性上绑定的事件处理函数执行
b. 然后浏览器会继续去事件监听对象队列中遍历查找当前元素的当前事件对应的监听对象。找到几个符合条件的监听对象,就执行几个其中的事件处理函数。
c. 还可以移除事件监听对象:
1). 元素对象.removeEventListener(“事件名”, 原事件处理函数)
2). 坑:如果添加事件监听对象时,使用匿名函数,移除事件监听对象时,也使用匿名函数,则无法移除事件监听对象。因为匿名函数的function是new Function()的意思,绑定时和移除时,分别执行了两次function(){},等效于先后new Function()创建了两个不同的函数。两个函数的地址肯定不同。所以无法移除添加事件监听时的原处理函数
3). 解决: 如果一个事件处理函数有可能被移除,则添加事件监听时就不能用匿名函数绑定,必须用有名称的函数来绑定。这样移除事件监听对象时,才能通过原函数名中保存的函数对象地址移除事件监听对象队列相同地址的事件处理函数
(4). 示例: 为发射按钮shoot添加多个事件监听,并移除事件监听:
<button id="btnShoot">shoot</button><br>
<button id="btnAward">获得跟踪导弹</button><br>
<button id="btnBreak">失去跟踪导弹</button><br>
<script>
var btnShoot=document.getElementById("btnShoot");
console.dir(btnShoot);
var btnAward=document.getElementById("btnAward");
var btnBreak=document.getElementById("btnBreak");
//点击shoot按钮时,输出发射xxx子弹
btnShoot.onclick=function(){
console.log(`发射普通子弹......`);
}
// 0x9091
function shoot2(){
alert(`发射跟踪导弹=>=>=>`)
}
//点击award按钮时,为shoot按钮多绑定一个事件处理函数多发射一种子弹
btnAward.onclick=function(){
//btnShoot.οnclick=function(){ 0x9091
btnShoot.addEventListener("click",shoot2);
}
//点击break按钮时,从shoot按钮上移除跟踪导弹的事件处理函数
btnBreak.onclick=function(){ // 0x9091
btnShoot.removeEventListener("click",shoot2)
}
</script>
(5). 问题: 浏览器的事件监听对象队列有一个规定:元素+事件名+处理函数地址,完全相同的事件监听对象只能创建一个!不能反复创建!
(6). 解决: 匿名函数无法移除,有名称的函数只能添加一次的矛盾!
扩展: 用数组保存多个匿名事件处理函数!
<button id="btnShoot">shoot</button><br>
<button id="btnAward">获得跟踪导弹</button><br>
<button id="btnBreak">失去跟踪导弹</button><br>
<script>
var btnShoot=document.getElementById("btnShoot");
console.dir(btnShoot);
var btnAward=document.getElementById("btnAward");
var btnBreak=document.getElementById("btnBreak");
var arr=[];//准备放所有事件监听对象的处理函数
//点击shoot按钮时,输出发射xxx子弹
btnShoot.onclick=function(){
console.log(`发射普通子弹......`);
}
//点击award按钮时,为shoot按钮多绑定一个事件处理函数多发射一种子弹
btnAward.onclick=function(){
//只要给shoot按钮添加新的处理函数时,都先将处理函数放入数组中
arr.push(function(){alert("发射跟踪导弹=>=>=>")})
//取出数组中最后一个事件处理函数,添加事件监听对象
btnShoot.addEventListener(
"click",arr[arr.length-1]
);
}
//点击break按钮时,从shoot按钮上移除跟踪导弹的事件处理函数
btnBreak.onclick=function(){
//先从数组中弹出pop()一个处理函数
var fun=arr.pop();
//然后用弹出的处理函数,去移除一个事件监听对象
btnShoot.removeEventListener("click",fun);
}
</script>
- 事件模型:
(1). 问题: 点在内层元素上,也会触发外层元素的事件处理函数
(2). 原因: 其实事件的触发过程是遵循一个模型的!
(3). 3个阶段:
a. 捕获: 从document根节点开始,到当前点击的元素结束,由外向内依次记录当前点击的元素的各级父元素上绑定的相同事件的处理函数都有哪些。——只记录不执行
b. 目标触发: 总是优先触发目标元素上的事件处理函数
1). 目标元素(target): 最初本意想点的那个元素!
c. 冒泡: 从当前目标元素开始,到顶级document结束,依次由内向外反向触发捕获阶段记录的各级父元素上的事件处理函数!
(4). 解决: 必须借助事件对象来解决: - 事件对象:
(1). 什么是:
a. 事件发生时浏览器自动创建的——不用我们手动创建
b. 保存事件相关信息的对象——内容
(2). 何时: 只要我们希望获得事件的相关信息,或者希望改变事件执行的默认行为或过程时,都要用事件对象
(3). 如何获取: 事件对象总是默认作为事件处理函数的第一个实参值,自动悄悄传入!所以:
事件发生时,浏览器创建event
↓
元素对象.οnclick=function(e){ … }
或元素对象.addEventListener(“事件名”,function(e){ … })
(4). 使用事件对象可以:
a. 取消冒泡:
1). e.stopPropagation()
停止 蔓延
2). 说明: e.stopPropagation()写在当前事件处理函数的开头和结尾没有任何差别!因为stopPropagation()防的不是自己的事件处理函数!防的是父级元素的事件处理函数。
3). 示例: 使用事件对象,取消冒泡:
<title>事件处理</title>
<meta charset="utf-8"/>
<style>
#d1 #d2 #d3{cursor:pointer}
#d1 {
background-color: green;
position: relative;
width: 150px;
height: 150px;
text-align: center;
cursor: pointer;
}
#d2 {
background-color: blue;
position: absolute;
top: 25px;
left: 175px;
width: 100px;
height: 100px;
}
#d3 {
background-color: red;
position: absolute;
top: 25px;
left: 225px;
width: 50px;
height: 50px;
line-height: 50px;
}
</style>
</head>
<body>
<div id="d1">
<div id="d2">
<div id="d3">
</div>
</div>
</div>
<script>
var d1=document.getElementById("d1");
var d2=document.getElementById("d2");
var d3=document.getElementById("d3");
//希望点谁,只有谁喊疼!
d1.onclick=function(){
alert("d1疼!")
}
d2.onclick=function(e){
//e.stopPropagation()
alert("d2疼!");
e.stopPropagation()
}
d3.onclick=function(e){
e.stopPropagation()
alert("d3疼!")
}
</script>
b. 利用冒泡/事件委托:
1). 优化: 尽量减少事件监听对象的个数!
2). 原因: 因为浏览器在触发事件时,是通过遍历事件监听对象队列的方式,查找到符合条件的事件监听对象,执行其中的事件处理函数。监听对象队列中的对象个数,决定了遍历的时长。监听对象越少,遍历越快!触发事件处理函数越及时!监听对象越多,遍历越慢!触发事件处理函数就有可能延迟!
3). 何时需要优化: 如果多个平级子元素都需要绑定相同的事件时,就需要用事件委托来优化
4). 如何: 3步:
a. 第一步: 只将事件绑定在父元素上一份即可!
事件委托: 正式因为父元素替所有孩子集中保管了共用的一个事件处理函数,所以这种做法也称为事件委托!
b. 问题: 因为事件冒泡到父元素上才执行,所以,当执行事件处理函数时,其中的this早就不指子元素了,而是指向父元素!我们不能再用this来获得实际点击的目标元素了!
c. 解决: 第二步: 用e.target代替this,获得最初点击的目标元素!
1). this会随冒泡而改变
2). e.target一旦保存住最初的目标元素,就不会随冒泡而改变!
d. 问题: 我们不希望父元素及其内部所有子元素都能触发事件!我们仅仅希望个别我们要求的元素才能触发事件!
e. 解决: 第三步: 先判断e.target的特征是不是想要的元素!只有e.target的特征是想要的元素时,才继续执行后续事件处理操作。如果e.target的特征不是我们想要的元素时,我们就什么也不干!
常用的特征: 标签名,className, 自定义属性值, 内容,…
补: 如何获得一个元素的标签名: 元素对象.nodeName
节点 名
坑: nodeName的节点名是全大写的!作比较做判断时,都要用全大写标签名进行比较!
-> BUTTON
-> A
f. 示例: 实现计算器效果:
<title>取消与利用冒泡</title>
<meta charset="utf-8"/>
</head>
<body>
<div id="keys">
<button>1</button><span>*</span>
<button>2</button><span>*</span>
<button>3</button><span>*</span>
<button>4</button><span>*</span><br>
<button>C</button><span>*</span>
<button>+</button><span>*</span>
<button>-</button><span>*</span>
<button>=</button><span>*</span>
</div>
<textarea id="sc" style="resize:none;width:200px; height:50px;" readonly></textarea>
<script>
//DOM 4步
//1. 查找触发事件的元素
//本例中,因为所有button都可单击,所以,应该利用事件委托优化,事件只绑定在父元素上一份即可!所有子元素通过冒泡机制,共用父元素上的事件处理函数
var div=document.getElementById("keys");
//2. 绑定事件处理函数
div.onclick=function(e){
//无论点哪个按钮,都会冒泡到父元素div,执行父元素div上的单击事件处理函数——事件委托
//alert("疼!");
//测试: 点哪个按钮,让哪个按钮变❀
//错误:
//this.innerHTML="❀";
//正确:
//问题: 不希望随便点哪里都能变❀,只希望点在按钮上时才变❀
if(e.target.nodeName==="BUTTON"){
// e.target.innerHTML="❀";
//3. 查找要修改的元素
//本例中:每次点击按钮,都要修改下方的文本框
//4. 修改元素
//先判断点击的按钮的内容
switch(e.target.innerHTML){
//如果是C,就清空显示屏文本框
case "C":
sc.value="";
break;
//如果是=,就将文本框内容,交给eval计算结果,再将结果放回显示屏中
case "=":
//错误处理: 复习第一阶段
try{//尝试执行
//eval: 可计算字符串类型的js表达式的值.——复习第一阶段
sc.value=eval(sc.value);
}catch(err){//如果出错
sc.value=err;//就把错误信息显示在文本框中
}
break;
//点击其余所有数字按钮和+ -号按钮,都只将按钮内容追加到文本框中算式结尾即可,不做计算
default:
sc.value+=e.target.innerHTML;
}
}
}
</script>
c. 阻止默认行为:
1). 问题: 有些元素上带有一些默认的行为,这些默认的行为是我们不想要的!
比如: xxx默认会做2件事:
i. 会在地址栏中url结尾自动添加#——和将来框架中的路由导航方式冲突
ii. 并不会留在原地,而是返回页面顶部!
2). 解决: e.preventDefault();
阻止 默认
3). 示例: 阻止a元素的默认性为:
<style>
div{
height:400px;
}
div:nth-child(1){
background-color:#afa;
}
div:nth-child(2){
background-color:#aaf;
}
div:nth-child(3){
background-color:#ffa;
}
div:nth-child(4){
background-color:#faf;
}
</style>
</head>
<body>
<div></div>
<div></div>
<div>
<a id="a1" href="#">我不是链接,我是一个按钮!</a>
</div>
<div></div>
<script>
var a1=document.getElementById("a1");
a1.onclick=function(e){
e.preventDefault();
alert("疼!");
}
</script>
d. 获取鼠标位置: 3组
1). 当前鼠标相对于屏幕左上角的距离/坐标:
e.screenX e.screenY
2). 当前鼠标相对于浏览器文档显示区左上角距离/坐标
e.clientX e.clientY
3). 当前鼠标相对于触发事件的元素左上角的距离/坐标
e.offsetX e.offsetY
4). 示例: 点击鼠标时获得鼠标位置:
<title>在当前显示区范围内实现点不到的小方块</title>
<style>
div{
position:fixed;
width:100px;
height:100px;
top:100px;
left:200px;
background-image:url(images/xiaoxin.gif);
background-size:100%;
}
</style>
</head>
<body>
<div id="pop"></div>
<script>
var div=document.getElementById("pop");
div.onclick=function(e){
console.log(e.screenX, e.screenY);
console.log(e.clientX, e.clientY);
console.log(e.offsetX, e.offsetY);
}
</script>
4. 页面滚动事件:
(1). 页面滚动时,会自动触发一个事件:
window.οnscrοll=function(){
(2). 获得页面滚动过的距离!固定套路!没有为什么!
var scrollTop=document.body.scrollTop
||document.documentElement.scrollTop;
}
(3). 示例: 页面滚动时,控制"回到顶部"链接显示隐藏:
<title>根据页面滚动位置显示浮动框</title>
<style>
body{height:2000px;}
#toTop{
position:fixed;
bottom:100px;
right:0;
display:none;
}
</style>
</head>
<body>
<div id="toTop">
<a href="#">返回顶部</a>
</div>
<script>
window.onscroll=function(){
var scrollTop=document.body.scrollTop
||document.documentElement.scrollTop;
console.log(scrollTop);
var div=document.getElementById("toTop");
//当滚动过的距离>=500时,让div显示出来
if(scrollTop>=500){
div.style.display="block";
}else{//当滚动过的额距离<500时,让div隐藏
div.style.display="none";
}
}
</script>
总结: 事件:
- 绑定事件: js中:
(1). 一个事件只绑定一个处理函数
元素.on事件名=function(){ … }
(2). 一个事件绑定多个处理函数
元素.addEventListener(“事件名”, 事件处理函数)
(3). 移除一个事件监听:
元素.removeEventListener(“事件名”, 原事件处理函数对象) - 事件模型: 捕获,目标触发,冒泡
- 事件对象:
(1). 获得事件对象:
元素.on事件名=function(e){ … }
(2). 阻止冒泡: e.stopPropagation()
(3). 当多个子元素都要绑定相同事件时,利用冒泡/事件委托3步:
a. 事件只在父元素上绑定一次
b. e.target代替this
c. 判断e.target的任意特征是否是我们想要的元素
(4). 阻止元素默认行为:
e.preventDefault()
(5). 获取鼠标位置:
a. 相对于屏幕左上角的x,y坐标:
e.screenX, e.screenY
b. 相对于文档显示区左上角的x,y用坐标:
e.clientX, e.clientY
c. 相对于事件所在元素左上角的x,y坐标:
e.offsetX e.offsetY
(6). 页面滚动事件:
window.οnscrοll=function(){
var scrollTop=document.documentElement.scrollTop||
document.body.scrollTop
//如果scrollTop>多少,就执行xx操作
//否则就恢复原样
}