一、事件绑定
1.1、HTML的 on- 属性
- 注意:HTML里面的事件属性值是将会执行的代码,而不是一个函数
<!-- on 加上事件名,比如 onload 就是 on + load -->
<body onload="doSomething()">
<!-- on 加上事件名,比如 onclick 就是 on + click -->
<div onclick="console.log('触发事件')">
1.2、元素的 .on 属性
- 注意:这种方法与 HTML 的on-属性的差异是,它的值是函数名
// 获取页面上的 div 标签
var oDiv = document.querySelector('div');
oDiv.onclick = function(){
//谁来触发事件 => oDiv => 这个事件的事件源就是oDiv
//触发什么事件 => onclick => 这个事件类型就是cLick
//触发之后做什么 => function(){} => 这个事件的处理函数
console.log('你点击了div'); // 每当点击一次,就会执行一次事件处理函数
}
- 当你点击的时候,只会执行第二个,第一个就没有了
- 我们还有一种事件监听的方式给元素绑定事件
- 使用
addEventListener
的方式添加- 这个方法不兼容,在IE里面要使用
attachEvent
- 这个方法不兼容,在IE里面要使用
1.3、事件监听
addEventListener(绑定事件)
addEventListener
:非IE 7、8使用
/*
target: 元素
type:事件类型,大小写敏感
listener:监听函数,事件发生时,会调用该监听函数。
useCapture:布尔值,默认false(冒泡触发),true(设置捕获触发)
*/
target.addEventListener(type, listener[, useCapture]);
- 示例:
oDiv.addEventListener('click',function(){
console.log('我是第一个事件');
},false)
oDiv.addEventListenner('click',function(){
console.log('我是第二个事件');
},false)
- 当你点击 div 的时候,两个函数都会执行,标签会按照你注册的顺序执行
- 先打印`我是第一个事件`再打印`我是第二个事件`
- 注意:事件的类型不要写on,点击事件就是click,不是onclick
attchEvent
:IE 7 8下使用- 语法:
元素.attachEvent('事件类型',事件处理函数);
oDiv.attachEvent('onclick',function(){
console.log("我是第一个事件");
})
oDiv.attackEvent('onclick',function(){
console.log('我是第二个事件');
})
- 当你点击div 的时候,两个函数都会执行,并且会按照你注册的顺序倒叙执行
- 先打印 我是第二个事件 再打印 我是第一个事件
- 注意:事件类型要写on,点击事件就写onclick
区别 | attachEvent | addEventListener |
---|---|---|
on | 要写on | 不用写on |
参数个数 | 2个参数 | 一般是3个常用参数 |
执行顺序 | 顺序注册,倒叙执行 | 顺序注册,顺序执行 |
兼容性 | IE 7 8浏览器 | 非IE 7 8的浏览器 |
removeEventListener(移除事件)
removeEventListener()
方法用来移除addEventListener()
方法添加的事件监听函数。该方法没有返回值。
div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);
removeEventListener()
方法的参数,与addEventListener()
方法完全一致
// 错误案例:监听函数不是同一个匿名函数,导致失效
div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);
// 错误案例:因为第三个参数不一样,导致失效
element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false);
dispatchEvent(触发事件)
dispatchEvent()
方法在当前节点上触发指定事件,从而触发监听函数的执行。- 该方法返回一个布尔值,只要有一个监听函数调用了
preventDefault()
,则返回值为false,否则为true
para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);
if (!para.dispatchEvent(event)) {
console.log('事件取消');
} else {
console.log('事件未取消');
}
- 案例:
const onMorePath = useCallback((e) => {
e.stopPropagation();
e.preventDefault();
const event = new MouseEvent('contextmenu', {
bubbles: true,
cancelable: false,
view: window,
button: 2,
buttons: 0,
clientX: e.currentTarget.getBoundingClientRect().x + 24,
clientY: e.currentTarget.getBoundingClientRect().y + 24,
});
e.currentTarget.dispatchEvent(event);
}, []);
二、事件传播
2.1、事件执行机制
- 什么是事件的执行机制呢?
- 思考一个问题:
- 当一个大盒子嵌套一个小盒子的时候,并且两个盒子都有点击事件
- 你点击里面的小盒子,外面的大盒子上的点击事件要不要执行
- 就像上面那个图片一样,我们点击在红色盒子身上的同时,也是点击在了粉色盒子上
- 这个是既定事实,那么两个盒子的点击事件都会触发
- 这个就叫做 事件的传播
var phases = {
1: 'capture', // 捕获
2: 'target', // 目标
3: 'bubble' // 冒泡
};
// 假设 div 为粉色大盒子
var div = document.querySelector('div');
// 假设 p 为粉色大盒子里面的红色盒子
var p = document.querySelector('p');
div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);
function callback(event) {
var tag = event.currentTarget.tagName;
var phase = phases[event.eventPhase];
console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}
// 点击 p 标签红色盒子以后的结果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'
- 注意:浏览器总是假定click事件的目标节点,就是点击位置嵌套最深的那个节点(本例是
<div>
节点里面的<p>
节点)。 - 所以:
<p>
节点的捕获阶段和冒泡阶段,都会显示为target
阶段,而<p>
节点有两个监听函数(一个捕获、一个冒泡),所以<p>
会在target
阶段有 2 次输出,而<div>
节点的捕获阶段和冒泡阶段各1次
捕获阶段:事件从`<div>`向`<p>`传播时,触发`<div>`的click事件;
目标阶段:事件从`<div>`到达`<p>`时,触发`<p>`的click事件;
冒泡阶段:事件从`<p>`传回`<div>`时,再次触发`<div>`的click事件。
- 上例的事件传播顺序为:
在捕获阶段依次为: window、document、html、body、div、p,
在冒泡阶段依次为: p、div、body、html、document、window。
2.2、冒泡、捕获、目标
一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
- 第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。
- 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
- 第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。
-
目标:你是点击在哪个元素身上了,那么这个事件的 目标 就是什么
-
冒泡:就是从事件 目标 的事件处理函数开始,依次向外,直到 window 的事件处理函数触发,也就是从下向上的执行事件处理函数
-
捕获:就是从 window 的事件处理函数开始,依次向内,只要事件 目标 的事件处理函数执行,也就是从上向下的执行事件处理函数
三、事件委托
- 就是把我要做的事情委托给别人来做
- 因为我们的冒泡机制,点击子元素的时候,也会同步触发父元素的相同事件
- 所以我们就可以把子元素的事件委托给父元素来做
3.1、为什么要用
- 点击子元素的时候,不管子元素有没有点击事件,只要父元素有点击事件,那么就可以出发父元素的点击事件
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var oUl = document.querySelector('ul');
oUL.addEventListener('click',function(e){
// 当你点击 ul 或 li 的时候都会触发
console.log('我是 ul 的点击事件,我被触发了');
})
</script>
</body>
- 为什么要用事件委托
1. 我页面上本身没有<li>, 我通过代码添加了一些<li>,添加进来的<li>是没有点击事件的
2. 我每次动态的操作完<li>以后都要从新给<li>绑定一次点击事件,比较麻烦
3. 这个时候只要委托给<ul>就可以了
4. 因为新加进来的<li>也是<ul>的子元素,点击的时候也可以触发<ul>的点击事件
- 事件委托的书写
元素的事件只能委托给 结构父级 或者 再结构父级 的 同样 的事件上
- <li>的点击事件,就不能委托给<ul>的鼠标 移入事件
- <li>的点击事件,只能委托给<ul>或者 在高父级的点击事件上
3.2、target 实现
- target 这个属性是事件对象里面的属性,表示你点击的目标
- 当你触发点击事件的时候,你点击在哪个元素上,target 就是哪个元素
- 这个 target 也不兼容,在 IE 下要使用 srcElement
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var oUl = docuemnt.querySelector('ul')
oUl.addEventListener('click', function (e) {
e = e || window.event
var target = e.target || e.srcElement
// 判断你点击的是 li
if (target.nodeName.toUpperCase === 'LI') {
// 确定点击的是 li
// 因为当你点击在 ul 上面的时候,nodeName 应该是 'UL'
// 我们就可以把 li 要做的事情委托给 ul 来做
console.log('我是 li,我被点击了')
}
})
</script>
</body>
四、阻止事件
4.1、阻止默认行为(preventDefault)
-
默认行为,就是不用我们注册,它自己就存在的事情
- 比如我们点击鼠标右键的时候,会自动弹出一个菜单
- 比如我们点击 a 标签的时候,我们不需要注册点击事件,他自己就会跳转页面
- 。。。
-
这些不需要我们注册就能实现的事情,我们叫做 默认事件
-
有的时候,我们不希望浏览器执行默认事件
- 比如我给 a 标签绑定了一个点击事件,我点击你的时候希望你能告诉我你的地址是什么
- 而不是直接跳转链接
- 那么我们就要把 a 标签原先的默认事件阻止,不让他执行默认事件
-
我们有两个方法来阻止默认事件
e.preventDefault()
: 非 IE 使用e.returnValue = false
:IE 使用
-
我们阻止默认事件的时候也要写一个兼容的写法
<a href="https://www.baidu.com">点击我试试</a>
<script>
var oA = document.querySelector('a')
a.addEventListener('click', function (e) {
e = e || window.event
console.log(this.href)
e.preventDefault ? e.preventDefault() : e.returnValue = false
})
</script>
- 这样写完以后,你点击 a 标签的时候,就不会跳转链接了
- 而是会在控制台打印出 a 标签的 href 属性的值
4.2、阻止事件传播(stopPropagation)
- 如果希望事件到某个节点为止,不再传播,可以使用事件对象的
stopPropagation
方法
// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener('click', function (event) {
event.stopPropagation();
}, true);
// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener('click', function (event) {
event.stopPropagation();
}, false);
stopPropagation
方法只会阻止事件的传播,不会阻止该事件触发<p>
节点的其他click事件的监听函数
p.addEventListener('click', function (event) {
event.stopPropagation();
console.log(1); // 能正常打印
});
// 另一个click事件的监听函数也会触发
p.addEventListener('click', function(event) {
// 会触发
console.log(2);
});
4.3、阻止事件触发(stopImmediatePropagation)
-
如果想要彻底取消该事件,不再触发后面所有click的监听函数,可以使用
stopImmediatePropagation
方法 -
stopImmediatePropagation
方法可以彻底取消这个事件,使得后面绑定的所有click监听函数都不再触发
p.addEventListener('click', function (event) {
event.stopImmediatePropagation();
console.log(1); // 能正常打印
});
// 另一个click事件的监听函数不会触发,因为在上一个监听函数中阻止了
p.addEventListener('click', function(event) {
// 不会被触发
console.log(2);
});
五、事件对象(Event)
5.1、基本介绍
- 什么是事件对象?
- 就是当你触发一个事件以后,对该事件的一些描述信息
- 例如:
- 你触发一个点击事件的时候,你点在哪个位置了,坐标是多少
- 你触发了一个键盘事件的时候,你按的是哪个按钮
- 。。。。。
- 每一个事件都会有一个对应的对象来描述这些信息,我们就把这个对象叫做事件对象
- 浏览器给了我们应该黑盒子,叫做
window.event
,就是对事件信息的所有描述- 比如点击事件
- 你点在了
0,0
位置,那么你得到的这个事件对象里面对应的就会有一个点位的属性 - 你点在了
10,10
位置,那么你得到的这个事件对象里面对应的就会有这个点位的属性 - 。。。
oDiv.onclick = function(){
console.log(window.event.X轴坐标点信息);
console.log(window.enent.Y轴坐标点信息);
}
- 这玩意很好用,但是一般来说,好用的东西就会有兼容性问题
- 在
IE低版本
里面这个东西好用,但是在高版本IE
和Chrome
里面不好使了 - 我们就得用另一种方式来获取事件对象
- 在每一个事件处理函数的形参位置,默认第一个就是事件对象
oDiv.onclick = function (e){
// e 就是和IE的window.event 一样的东西
console.log(e.X轴的坐标点信息);
console.log(e.Y轴坐标点信息);
}
- 综上所述,我们以后每一个事件里面,想要获取事件对象的时候,都用兼容写法
oDiv.onclick = function(e){
e = e || window.event;
console.log(e.X轴坐标点的信息);
console.log(e.Y轴坐标点的信息);
}
5.2、坐标获取(示例)
- 刚才既然说了,可以获取到坐标点,那么接下来我们就学习一下怎么获取坐标点
- 我们的每一个点击事件的坐标点都不是一对,因为要有一个相对的坐标系
- 例如:
- 相对事件源(你点击的元素)
- 相对页面
- 相对浏览器窗口
- 。。。。。
- 因为都不一样,所以我们获取的事件对象里面的属性也不一样
相对于你点击的元素
offsetX
和offsetY
- 是相对于你点击的元素的边框内侧开始计算
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 300px;
height: 300px;
padding: 20px;
border: 10px solid #333;
margin: 20px 0 0 30px;
}
</style>
<body>
<div></div>
<script>
var oDiv = document.querySelector('div')
//注册点击事件
oDiv.onclick = function(e){
//事件对象兼容写法
e = e || window.event;
console.log(e.offsetX);
console.log(e.offsetY);
}
</script>
</body>
相对于浏览器可视窗口你点击的坐标点
clientX
和clientY
- 是相当于浏览器窗口来计算的,不管你页面滚动到什么情况,都是根据可视窗口来计算坐标
<style>
* {
margin: 0;
padding: 0;
}
body {
width: 2000px;
height: 2000px;
}
div {
width: 300px;
height: 300px;
padding: 20px;
border: 10px solid #333;
margin: 20px 0 0 30px;
}
</style>
<body>
<div></div>
<script>
var oDiv = document.querySelector('div');
//注册点击事件
oDiv.onclick = function(e){
//事件对象兼容写法
e = e || window.event;
console.log(e.clientX);
console.log(e.clientY);
}
</script>
</body>
相当于页面你点击的坐标点
pageX
和pageY
- 是相对于整个页面的坐标点,不管有没有滚动,都是相对于页面拿到的坐标点
<style>
* {
margin: 0;
padding: 0;
}
body {
width: 2000px;
height: 2000px;
}
div {
width: 300px;
height: 300px;
padding: 20px;
border: 10px solid #333;
margin: 20px 0 0 30px;
}
</style>
<body>
<div></div>
<script>
var oDiv = document.querySelector('div');
//注册点击事件
oDiv.onclick = function(e){
//事件对象兼容写法
e = e || window.event;
console.log(e.pageX);
console.log(e.pageY);
}
</script>
</body>