目录
1.案例引入事件流:
<body>
<style>
*{margin: 0;}
.box1{
width: 400px;
height: 300px;
background-color: brown;
cursor: pointer;
position:relative;
left: 100px;
top: 20px;
}
.box2{
width: 200px;
height: 200px;
background-color: red;
cursor: pointer;
position:absolute;
left: 10px;
top: 20px;
}
.box3{
width: 100px;
height: 100px;
background-color: gold;
cursor: pointer;
margin: 10px;
padding: 5px;
border: 3px solid saddlebrown;
}
</style>
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
<script>
var box1 = document.querySelector('.box1');
var box2 = document.querySelector('.box2');
var box3 = document.querySelector('.box3');
box1.onclick = function(){
console.log('box111111');
}
</script>
</body>
分析:只给box1绑定点击事件,点击box1的区域,包括box2和box3(都在box1的区域内),都会打印box111111
利用绝对定位把box2和box3放到box1区域红色区域外面去,再点击box2和box3会出现什么效果?
<body>
<style>
*{margin: 0;}
.box1{
width: 400px;
height: 300px;
background-color: brown;
cursor: pointer;
position:relative;
left: 100px;
top: 20px;
}
.box2{
width: 200px;
height: 200px;
background-color: red;
cursor: pointer;
/* 让box2带着box3从box1的区域出去 */
position:absolute;
left: 500px;
top: 20px;
}
.box3{
width: 100px;
height: 100px;
background-color: gold;
cursor: pointer;
margin: 10px;
padding: 5px;
border: 3px solid saddlebrown;
}
</style>
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
<script>
var box1 = document.querySelector('.box1');
var box2 = document.querySelector('.box2');
var box3 = document.querySelector('.box3');
box1.onclick = function(){
console.log('box111111');
}
</script>
</body>
分析:只给了box1绑定点击事件,然后把box2和box3移到box1外面的区域,为什么点击box2和box3还是会打印box111111。原因是box2是box1的子元素,box3又是box2的子元素,虽然通过绝对定位移出去了,但是还是box1的子元素。所以我们要研究事件是怎么执行的,跟父子关系有什么联系?
事件对象的path属性:
<body>
<style>
*{margin: 0;}
.box1{
width: 400px;
height: 300px;
background-color: brown;
cursor: pointer;
position:relative;
left: 100px;
top: 20px;
}
.box2{
width: 200px;
height: 200px;
background-color: red;
cursor: pointer;
/* 让box2带着box3从box1的区域出去 */
position:absolute;
left: 500px;
top: 20px;
}
.box3{
width: 100px;
height: 100px;
background-color: gold;
cursor: pointer;
margin: 10px;
padding: 5px;
border: 3px solid saddlebrown;
}
</style>
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
<script>
var box1 = document.querySelector('.box1');
var box2 = document.querySelector('.box2');
var box3 = document.querySelector('.box3');
box1.onclick = function(e){
//e是事件对象
console.log('box111111',e);
}
</script>
</body>
分析:点击了box3,触发了box1的点击事件,生成的事件对象,然后查看其中的path属性
就是点击事件所经过的层级(嵌套关系)。
但通常,一个事件会从父元素开始向目标元素传播(捕获),然后它将被传播回父元素(冒泡)。这个过程就是事件流/事件链.
2.事件流
DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
- 捕获阶段:事件从父元素开始向目标元素传播,从
Window
对象开始传播。- 目标阶段:该事件到达目标元素或开始该事件的元素。
- 冒泡阶段:这时与捕获阶段相反,事件向父元素传播,直到
Window
对象
无论是事件捕获还是事件冒泡,它们都有一个共同的行为,就是事件传播。
dom标准事件流的触发的先后顺序为:先捕获再冒泡。即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。
所以上面最开始引入的例子:为什么点击box3,也会执行给box1绑定的点击事件?
因为:点击box3时,因为 box1的点击事件在捕获阶段并没有触发(addEventListener第3个参数没写或者写false代表冒泡阶段触发),然后一直沿着父子关系向下传递,直到最下面的box3,然后进入冒泡阶段,因为点击是点到box3,所以触发了box1传下来的点击事件,执行box1绑定的点击事件,打印box11111,所以 box3就是目标(target:谁触发得事件target就是谁 event.target = box3)
如果点击的是box2,事件经过的path,最底层就是box2,然后进入冒泡阶段,就不会在捕获阶段进入box3然后再从box3进入冒泡阶段:
如果是点击box1,事件经过的path,最底层就是box1,然后进入冒泡阶段,就不会在捕获阶段进入box3然后再从box3进入冒泡阶段:
如果点击box1的父元素body,直接就不会执行点击事件。
3.证明事件执行顺序是按事件流来的
<body>
<style>
*{margin: 0;}
.box1{
width: 400px;
height: 300px;
background-color: brown;
cursor: pointer;
position:relative;
left: 100px;
top: 20px;
}
.box2{
width: 200px;
height: 200px;
background-color: red;
cursor: pointer;
/* 让box2带着box3从box1的区域出去 */
position:absolute;
left: 500px;
top: 20px;
}
.box3{
width: 100px;
height: 100px;
background-color: gold;
cursor: pointer;
margin: 10px;
padding: 5px;
border: 3px solid saddlebrown;
}
</style>
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
<script>
var box1 = document.querySelector('.box1');
var box2 = document.querySelector('.box2');
var box3 = document.querySelector('.box3');
box1.onclick = function(e){
//e是事件对象
console.log('box111111',e);
}
box2.onclick = function(e){
//e是事件对象
console.log('box222222',e);
}
box3.onclick = function(e){
//e是事件对象
console.log('box333333',e);
}
</script>
</body>
分析:
点击box1,打印box111111 PointerEvent {}
点击box2:打印
box22222 PointerEvent {}
box111111 PointerEvent {}
点击box3:打印
box33333 PointerEvent {}
box22222 PointerEvent {}
box111111 PointerEvent {}
结合事件流,就可以很容易理解这个打印结果。
3.addEventListener的第三个参数
在我们平常用的addEventListener
方法中,一般只会用到两个参数,一个是需要绑定的事件,另一个是触发事件后要执行的函数,然而,addEventListener
还可以传入第三个参数:
第三个参数默认是false,也就是第三个参数不写或者写false: 表示在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。
//给box1绑定两个事件,一个捕获阶段触发,一个冒泡阶段触发
box1.addEventListener('click',(e)=>{
console.log('box111111bbbbbbb',e);
},true)
box1.addEventListener('click',(e)=>{
console.log('box111111bbbbbbb',e);
},false)
问题: addEventListener的第三个参数为true是阻止事件传递还是false是阻止事件传递?
答案:都不会阻止事件传递,因为true捕获阶段触发,false冒泡阶段触发
要阻止事件传递,唯一的方式就是阻止事件冒泡,事件对象调用stopPropagation();
4.阻止事件冒泡和默认事件 是事件对象的
扩展一点:event.eventPhase 有3个值,对于事件3阶段 捕获 目标 冒泡
1.event.stopPropagation()方法 阻止事件的冒泡方法
这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件任然会执行。eg:当你调用这个方法的时候,如果点击一个连接,这个连接仍然会被打开,
2.event.preventDefault()方法 阻止默认事件
这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;
<body>
<style>
.box1{
width: 200px;
height: 200px;
background-color: blue;
}
.box2{
width: 100px;
height: 100px;
background-color: pink;
}
</style>
<div class="box1">
<div class="box2">
<a href="https://www.baidu.com" id="a">跳到百度</a>
</div>
</div>
<script>
var box1 = document.querySelector('.box1');
var a = document.querySelector('#a');
box1.addEventListener('click',(e)=>{
console.log('box1111111');
})
a.addEventListener('click',(e)=>{
console.log(666);
//stopPropagation 阻止冒泡 但不阻止默认行为
e.stopPropagation();
//阻止默认行为,但不阻止冒泡 所以要想阻止默认行为,又要阻止冒泡,两个都写
e.preventDefault();
})
</script>
</body>
分析:a标签的默认行为,就是其默认有一个点击事件(官方设置的),点击它会跳转到相应的href地址去,如果通过 event.stopPropagation()方法 和event.preventDefault()方法设置了阻止冒泡和阻止默认行为后,之后打印 666
3.return false ;
这个方法比较暴力,他会同时阻止事件冒泡也会阻止默认事件
但是需要注意的是:
IE:
window.event.cancelBubble = true;//停止冒泡
window.event.returnValue = false;//阻止事件的默认行为
Firefox(与谷歌一样):
event.preventDefault();// 阻止事件的默认行为
event.stopPropagation(); // 阻止事件的传播
4.event.stopImmediatePropagation() 阻止事件冒泡 也阻止同一类型的其它事件程序(阻止同一事件的多次绑定)从它这里过(同一类型的冒泡),阻止不了默认事件这些哈!!
<body>
<style>
*{margin: 0;}
.box1{
width: 400px;
height: 300px;
background-color: brown;
cursor: pointer;
position:relative;
left: 100px;
top: 20px;
}
.box2{
width: 200px;
height: 200px;
background-color: red;
cursor: pointer;
/* 让box2带着box3从box1的区域出去 */
position:absolute;
left: 500px;
top: 20px;
}
.box3{
width: 100px;
height: 100px;
background-color: gold;
cursor: pointer;
margin: 10px;
padding: 5px;
border: 3px solid saddlebrown;
}
</style>
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
<script>
var box1 = document.querySelector('.box1');
var box2 = document.querySelector('.box2');
var box3 = document.querySelector('.box3');
box1.addEventListener('click',(e)=>{
console.log('box111111',e);
})
box3.addEventListener('click',(e)=>{
console.log('box3333aaaaaa',e);
e.stopPropagation();
// e.stopImmediatePropagation();
})
box3.addEventListener('click',(e)=>{
console.log('box3333bbbbb',e);
})
</script>
</body>
分析:点击box3时,是阻止冒泡的情况下:e.stopPropagation();打印出
box3333aaaaaa PointerEvent{}
box3333bbbbbb PointerEvent{} 还好,虽然阻止冒泡,但至少本身其它的同一类型事件还能过。
点击box3时,阻止事件冒泡 也阻止同一类型的其它事件程序从它这里过 情况下 :e.stopImmediatePropagation();打印出
box3333aaaaaa PointerEvent{}
e.stopImmediatePropagation(); 就连本身绑定同一类型的第二个点击事件都经过不了,更不说冒泡了。
5.阻止事件冒泡和阻止事件默认情况的兼容性写法
1.阻止事件默认情况的兼容性写法
event.preventDefault ? event.preventDefault() : event.returnValue = false;
eg:阻止a标签的默认事件和阻止鼠标右键点击的默认事件
<body>
<a href="https://www.baidu.com/">百度</a>
<script>
document.querySelector('a').addEventListener('click',function(){
event.preventDefault ? event.preventDefault() : event.returnValue = false;
console.log(111);
});
// contextmenu:鼠标右键事件
window.oncontextmenu = function(){
event.preventDefault ? event.preventDefault() : event.returnValue = false;
console.log("鼠标右键事件");
}
</script>
</body>
2.阻止事件冒泡的兼容性写法
event.stopPropagation?event.stopPropagation():event.cancelBubble = true;
5.易错点
1.
<body>
<style>
*{margin: 0;}
.box1{
width: 400px;
height: 300px;
background-color: brown;
cursor: pointer;
position:relative;
left: 100px;
top: 20px;
}
.box2{
width: 200px;
height: 200px;
background-color: red;
cursor: pointer;
/* 让box2带着box3从box1的区域出去 */
position:absolute;
left: 500px;
top: 20px;
}
.box3{
width: 100px;
height: 100px;
background-color: gold;
cursor: pointer;
margin: 10px;
padding: 5px;
border: 3px solid saddlebrown;
}
</style>
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
<script>
var box1 = document.querySelector('.box1');
var box2 = document.querySelector('.box2');
var box3 = document.querySelector('.box3');
box1.addEventListener('click',(e)=>{
console.log('box111111',e);
})
box2.addEventListener('mousedown',(e)=>{
console.log('box22222',e);
})
box3.addEventListener('click',(e)=>{
console.log('box3333aaaaaa',e);
// e.stopPropagation();
e.stopImmediatePropagation();
})
box3.addEventListener('click',(e)=>{
console.log('box3333bbbbb',e);
})
</script>
</body>
分析:box3中是针对于 click 类型的事件阻止冒泡和阻止 click事件经过。但并不阻止 mousedown 类型事件通过。所以,点击box3区域,不管是e.stopPropagation();还是e.stopImmediatePropagation();这种情况下,都能打印出 box22222 PointerEvent{ }
2.
document.querySelector('.box1').onclick = function(){
console.log("第一次给box1绑定click事件");
}
document.querySelector('.box1').onclick = function(){
console.log("第二次给box1绑定click事件");
}
分析:只能打印出 "第二次给box1绑定click事件" 这就相对于给一个对象添加属性,同一属性添加两次,第二次就是修改该属性的值,所以只打印 "第二次给box1绑定click事件"
不支持冒泡的事件:
UI事件:
load
unload
resize
abort
error
焦点事件:
blur
focus
鼠标事件:
mouseleave
mouseenter
有一个比较特殊:
scroll 事件:element的scroll事件不冒泡, 但是document的defaultView的scroll事件冒泡