事件流(事件传播)
事件流:描述的是从页面中接收事件的顺序。
三个阶段:
- 事件事件捕获
- 处于目标阶段
- 事件冒泡阶段
历史:
如果单击了某个按钮,他们认为单击事件不仅仅发生在按钮上,甚至单击整个页面。
- 就像上面那个图片一样,我们点击在红色盒子身上的同时,也是点击在了粉色盒子上
- 这个是既定事实,那么两个盒子的点击事件都会触发
- 这个就叫做 事件的传播
IE事件流:事件冒泡
Netscape(网景):事件捕获
事件冒泡
事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点(文档)
<!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>
#box {
width:200px;
height:200px;
background-color:red;
}
</style>
</head>
<body>
<div id="box">
</div>
<script>
var box = document.getElementById('box');
box.onclick = function() {
box.innerHTML += 'div\n';
}
document.body.onclick = function() {
box.innerHTML +='body\n';
}
document.documentElement.onclick = function() {
box.innerHTML += 'html\n';
}
document.onclick = function() {
box.innerHTML += 'document\n';
}
window.onclick = function() {
box.innerHTML += 'window\n';
}
</script>
</body>
</html>
当我们点击红盒子后:
这就说明,事件冒泡就是从目标元素逐层往上传递,直到window。
事件捕获
由不太具体的节点更早的接收事件,更具体的节点应该最后接收到事件。
直白说就是最先是window接收,最后是目标元素接收。
<!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>
#box {
width:200px;
height:200px;
background-color:red;
}
</style>
</head>
<body>
<div id="box">
</div>
<script>
// 默认为false,冒泡阶段 true为捕获阶段
var box = document.getElementById('box');
box.addEventListener('click',function(){
box.innerHTML += 'div\n';
},true)
document.body.addEventListener('click',function(){
box.innerHTML +='body\n';
}.true)
document.documentElement.addEventListener('click',function(){
box.innerHTML +='html\n';
}.true)
document.addEventListener('click',function(){
box.innerHTML +='document\n';
}.true)
window.addEventListener('click',function(){
box.innerHTML +='window\n';
}.true)
</script>
</body>
</html>
默认:(原始)
点击红盒子后:
结论:事件捕获是从window开始逐层往下传播的,直到目标元素。
传播顺序
先捕获,再目标,再冒泡。也就是说同一元素实际上会被触发两次。但是浏览器默认使用的都是“冒泡”阶段,也就是说,我们绑定事件后一般只会触发一次。
事件处理程序
事件处理程序
- HTML
- DOM0级
- DOM2级
- IE事件处理程序
HTML
<!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>
#box {
width:200px;
height:200px;
background-color:red;
}
</style>
</head>
<body>
<!-- <div id="box" onclick="this.innerHTML += '1'"></div> -->
<div id="box" onclick ='test();'></div>
<script>
function test() {
duocument.getElementById('box').innerHTML += '1';
}
</script>
</body>
</html>
缺点:html和js无分离,后期不易维护
DOM0
DOM0就是使用dom.onclick = function() {}
优势:简单 具有跨浏览器的优势
特点:只有冒泡阶段,没有捕获阶段
缺点:不能给同一个元素来绑定相同的事件处理程序,如果绑定了,会有覆盖现象。
删除事件处理程序:dom.onclick = null;
DOM2
DOM2
-
增加事件监听器
dom.addEventListener('click',function(事件名,处理程序的函数,布尔值){})- 布尔值默认是false,处于冒泡
- 如果为true,则处于捕获阶段
-
移除事件监听器
dom.removeEventListener()
如果移除事件,就必须在增加事件监听器时,将处理程序的函数写成具名的
let box = document.querySelector('#box');
const handler = function() {
this.innerHTML += '1'
}
box.addEventListener('click',handler);
box.removeEventListener('click',handler);
// 以下是错误的,这样并不能移除掉
box.addEventListener('click',function() {
this.innerHTML += '1'
});
box.removeEventListener('click',function() {
this.innerHTML += '1'
});
IE
仅仅有冒泡流:绑定attachEvent() 移除detachEvent()
let box = document.querySelector('#box');
box.attachEvent('onclick',function(){
this.innerHTML += '1'
})
算了不写了,这个只能在IE浏览器中使用,有锤子用,IE都挂了...
如何获取事件对象
window.onload = function() {
var box = ducument.querySelector('#box');
box.onclick = function(e) {
console.log(e);
}
}
event对象是事件处理程序的第一个参数 ie8浏览器不支持
2/直接使用event变量
box.onclick = function() {
console.log(event)
}
事件目标对象
currentTarget target和srcElement
- currentTarget 属性返回事件当前所在的节点,正在执行的监听函数所绑定的节点 (就是事件绑定的节点)
- target返回的是事件的实际触发目标对象
- this对象和e.currentTarget属性是一致的,指向的都是绑定这个事件的节点
- srcElement是IE里的,IE8以下不兼容target,就需要使用secElement来获取,但是现在不重要了...
<!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>
* {
margin: 0;
padding: 0;
}
#box {
width: 200px;
height: 200px;
background-color: red;
}
li {
list-style: none;
width: 100%;
height: 20%;
background-color: black;
color: #fff;
margin-bottom: 10px;
}
#box li:nth-child(odd) {
background-color: blue;
}
</style>
</head>
<body>
<ul id="box">
<li class="item">1</li>
<li class="item">2</li>
</ul>
<script>
let box = document.querySelector('#box');
box.onclick = function (e) {
e = e || window.event; // window.event的为了兼容IE
console.log('e.target:', e.target)
console.log('e.currentTarget:', e.currentTarget)
}
</script>
</body>
</html>
当我们点击1时:
当我们点击2时:
当我们点击红色区域时:
通过这个例子我们就应该能很好的理解e.target与e.currentTarget的区别了吧!我们绑定事件绑定的就是在ul上面,所以不管我们点击ul里面的哪里,只要这个事件触发了,e.currentTarget指向的就是ul;而e.target就是指的是这个事件真正的触发者,因为我们点击的是<li class="item"></li>这个元素。其实这就是事件委托或者事件代理。
事件委托(事件代理)
- 就是把我要做的事情委托给别人来做
- 因为我们的冒泡机制,点击子元素的时候,也会同步触发父元素的相同事件
- 所以我们就可以把子元素的事件委托给父元素来做
上面的例子其实已经很好的解释了什么是事件委托以及事件代理。
事件委托的好处:
-
点击子元素的时候,不管子元素有没有点击事件,只要父元素有点击事件,那么就可以触发父元素的点击事件。
-
为什么要用事件委托:
- 我页面上本身没有
<li>, 我通过代码添加了一些<li>,添加进来的<li>是没有点击事件的 - 我每次动态的操作完
<li>以后都要从新给<li>绑定一次点击事件,比较麻烦 - 这个时候只要委托给
<ul>就可以了 - 因为新加进来的
<li>也是<ul>的子元素,点击的时候也可以触发<ul>的点击事件
- 我页面上本身没有
-
事件委托的书写:
- 元素的事件只能委托给 结构父级 或者 再结构父级 的 相同事件 上
<li>的点击事件,就不能委托给<ul>的鼠标 移入事件<li>的点击事件,只能委托给<ul>或者 在高父级的点击事件上
- 元素的事件只能委托给 结构父级 或者 再结构父级 的 相同事件 上
阻止事件
阻止默认行为
我们知道我们的<a></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 属性的值
阻止事件传播
- 如果希望事件到某个节点为止,不再传播,可以使用事件对象的
stopPropagation方法
// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener('click', function (event) {
event.stopPropagation();
}, true);
// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener('click', function (event) {
event.stopPropagation();
}, false);
例子:
<!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>
#box {
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<div id="box">
</div>
<script>
// 默认为false,冒泡阶段 true为捕获阶段
var box = document.getElementById('box');
box.addEventListener('click', function (event) {
box.innerHTML += 'div\n';
}, true)
document.body.addEventListener('click', function (event) {
event.stopPropagation();
box.innerHTML += 'body\n';
}, true)
document.documentElement.addEventListener('click', function () {
box.innerHTML += 'html\n';
}, true)
</script>
</body>
</html>
当我们给body添加stopPropagation方法后,那么body就停止了事件传播行为,所以当我们点击div时,由于是捕获行为,就从window往下传播到body就停止传播了,而这里只有html、body、div绑定了事件,所有红盒子里输出了html body.
stopPropagation方法只会阻止事件的传播,不会阻止该事件触发<p>节点的其他click事件的监听函数
p.addEventListener('click', function (event) {
event.stopPropagation();
console.log(1); // 能正常打印
});
// 另一个click事件的监听函数也会触发
p.addEventListener('click', function(event) {
// 会触发
console.log(2);
});
阻止事件触发
- 如果想要彻底取消该事件,不再触发后面所有click的监听函数,可以使用
stopImmediatePropagation方法 stopImmediatePropagation方法可以彻底取消这个事件,使得后面绑定的所有click监听函数都不再触发
p.addEventListener('click', function (event) {
event.stopImmediatePropagation();
console.log(1); // 能正常打印
});
// 另一个click事件的监听函数不会触发,因为在上一个监听函数中阻止了
p.addEventListener('click', function(event) {
// 不会被触发
console.log(2);
});
但是该方法不会阻止事件的传播,只会阻止事件的触发。
常见事件
- 大致分为几类,浏览器事件、鼠标事件、键盘事件、表单事件、触摸事件
浏览器事件
用的最多的就是window.onload了吧...就是等所有资源都加载完毕后再执行。包括图片资源、css资源等。
说到这里,还有一个document.ready(Jquery提供的事件),它们两者之间有什么区别吗??
-
这就要说一说浏览器的渲染机制了,浏览器在加载HTML时,会先将HTML解析成为DOM树,在和css解析成的CSSOM一起行为渲染树,再对渲染树进行layout布局生成布局树Layout tree,最后再通过显卡将Layout Tree进行绘制,显示在屏幕上。
-
而当HTML成功解析为DOM树时,浏览器自带了一个事件
DOMContentLoaded,document.ready触发的时候就是调用的DOMContentLoaded,所以document.ready触发的时候就是DOM树生成的时候。 -
而
window.onload就是等所有资源加载完成后才执行。 -
所以
window.onload执行事件比document.ready晚。
我在网上找的一张图。document.ready是时间点1,window.onload是时间点2。
鼠标事件
鼠标点击事件:
| 事件类型 | 说明 |
|---|---|
| click | 单击鼠标左键时发生,如果右键也按下则不会发生。当用户的焦点在按钮上并按了 Enter 键时,同样会触发这个事件 |
| dblclick | 双击鼠标左键时发生,如果右键也按下则不会发生 |
| mousedown | 单击任意一个鼠标按钮时发生 |
| mouseup | 松开任意一个鼠标按钮时发生 |
鼠标移动事件:
| 事件类型 | 说明 |
|---|---|
| mousemove | 当鼠标在一个节点内部移动时触发 |
| mouseout | 鼠标离开一个节点时触发,离开父节点也会触发这个事件 |
| mouseover | 鼠标进入一个节点时触发,进入子节点会再一次触发这个事件 |
| mouseleave | 鼠标离开一个节点时触发,离开父节点不会触发这个事件 |
| mouseenter | 鼠标进入一个节点时触发,进入子节点不会触发这个事件 |
注意:
mouseleave和mouseenter是不会有事件传播的。
键盘事件
| 事件类型 | 描述 |
|---|---|
| keydown | 键盘按下事件 |
| keypress | 按下有值的键时触发,即按下 Ctrl、Alt、Shift、Meta 这样无值的键,这个事件不会触发 |
| keyup | 键盘抬起事件 |
鼠标点击事件触发顺序是:
- keydown 首先触发 2. keypress 接着触发 3. keyup 最后触发
表单事件
| 事件类型 | 描述 |
|---|---|
| input | 每次输入数据都会触发事件 |
| select | elect事件当在< input>、< textarea>里面选中文本时触发 |
| change | 该事件在表单元素的内容改变且失去焦点时触发 |
| invalid | 用户提交表单时,如果表单元素的值不满足校验条件,就会触发invalid事件 |
| reset | reset事件当表单重置(所有表单成员变回默认值)时触发 |
| submit | 提交表单时触发 |
| focus | 获取焦点时触发 |
| blur | 失去焦点时触发 |
input
- 对于复选框或单选框,用户改变选项时,也会触发这个事件;
- input事件的一个特点,就是会连续触发,比如用户每按下一次按键,就会触发一次input事件;
/* HTML 代码如下
<select id="mySelect">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
*/
function inputHandler(e) {
console.log(e.target.value)
}
var mySelect = document.querySelector('#mySelect');
mySelect.addEventListener('input', inputHandler);
上面代码中,改变下拉框选项时,会触发input事件,从而执行回调函数inputHandler。
selected
// HTML 代码如下
// <input id="test" type="text" value="Select me!" />
var elem = document.getElementById('test');
elem.addEventListener('select', function (e) {
console.log(e.type); // "select"
}, false);
选中的文本可以通过event.target元素的selectionDirection、selectionEnd、selectionStart和value属性拿到。
change
- 激活单选框(radio)或复选框(checkbox)时触发。
- 用户提交时触发。比如,从下列列表(select)完成选择,在日期或文件输入框完成选择。
- 当文本框或元素的值发生改变,并且丧失焦点时触发。
与input的区别:
不同之处在于input事件在元素的值发生变化后立即发生,而change在元素失去焦点时发生,而内容此时可能已经变化多次。也就是说,如果有连续变化,input事件会触发多次,而change事件只在失去焦点时触发一次。(可以看作两者之间的区别只有是input时才有)
// HTML 代码如下
// <select size="1" οnchange="changeEventHandler(event);">
// <option>chocolate</option>
// <option>strawberry</option>
// <option>vanilla</option>
// </select>
function changeEventHandler(event) {
console.log(event.target.value);
}
如果比较一下上面input事件的例子,你会发现对于元素来说,input和change事件基本是等价的。
invalid
<form>
<input type="text" required oninvalid="console.log('invalid input')" />
<button type="submit">提交</button>
</form>
上面代码中,输入框是必填的。如果不填,用户点击按钮提交时,就会触发输入框的invalid事件,导致提交被取消。
如果使用过Element UI或者Element Plus的表单,就会对这个很熟悉啦~
blur和focus
blur:某元素失去活动焦点时产生该事件。例如鼠标在文本框中点击后又在文本框外点击时就会产生。
focus:网页上的元素获得焦点时产生该事件。
blur和focus不能事件冒泡。
触摸事件
主要用于移动端,这个就不写啦,主要是没咋用过,等后面用了再说~
Event获取鼠标位置
offsetX与offsetY
- 是相对于你点击的元素的边框内侧开始计算。
<!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>
#box {
margin: 100px;
width: 200px;
height: 200px;
background-color: red;
border:5px solid #000;
}
.sm {
width: 50px;
height: 50px;
background-color: #000;
margin: 30px;
box-sizing: border-box;
border: 5px solid green;
}
</style>
</head>
<body>
<div id="box">
<div class="sm"></div>
</div>
<script>
let box = document.getElementById('box');
box.addEventListener('click',(e)=>{
console.log('X:',e.offsetX,'Y:',e.offsetY);
})
</script>
</body>
</html>
我们发现它的X与Y是基于事件真正触发的元素的border内侧开始计算的(也就是e.target),而不是基于绑定事件的元素的内边框。
clientX与clientY
- 是相当于浏览器窗口来计算的,不管你页面滚动到什么情况,都是根据可视窗口来计算坐标
就是从电脑屏幕的左上角开始计算。
pageX与pageY
- 是相对于整个页面的坐标点,不管有没有滚动,都是相对于页面拿到的坐标点
就是从页面的左上角开始计算,如果滚动条没有滚动,那么等于clientX与clientY。(应该可以理解吧?这个上一节js写过的offset、client、scroll和这个差不多。)
马上js就写完了,哈哈...😉😁