事件
事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。在将事件流,也就是事件发生顺序之前,我们先来看看DOM树
1、DOM树
DOM树是怎么形成的呢?我们在VScode中写的代码会通过专门的HTML解析器将HTML文档解析成DOM树结构,DOM树的根节点是document对象。HTML解析器在解析过程中,在中发现了引入文件,于是向服务器请求文件,注意link文件在请求和下载文件过程中将继续向下解析HTML,当引入文件下载完成后会通知浏览器回头来解析;是非阻塞的。而script文件会等待文件下载完成,立即执行,执行完毕后再向下解析,是阻塞的。因此要将css文件放置于顶端,将script标签置于body底端。
比如:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<a href="#">我的链接</a>
<h1>我的标题</h1>
</body>
</html>
那么通过HTML解析器解析后的DOM树为:
2、 DOM事件流
DOM事件流包括事件捕获阶段、目标阶段、冒泡阶段。
事件的捕获阶段是从DOM树的根节点一直往下找,目标阶段是找到被点击的元素,冒泡阶段是找到元素以后一层层的网上冒,告诉浏览器找到了被点击的元素。
比如:
3、事件流验证
3.1 捕获阶段
被点击的是儿子,那么依据事件流的捕获原理,那么包裹的最外层的爷爷如果绑定了点击事件,该事件也会被执行,所以会出现先执行父亲的点击事件,再执行爸爸的点击事件,然后执行儿子的点击事件。
<div id="grandpa">
父亲
<div id="father">
爸爸
<div id="son">
儿子
</div>
</div>
</div>
<script>
let grandpa = document.getElementById('grandpa');
let father = document.getElementById('father');
let son = document.getElementById('son');
// true是开启事件流捕获
grandpa.addEventListener('click', function () {
console.log('我是爷爷');
}, true)
father.addEventListener('click', function () {
console.log('我是爸爸');
}, true)
son.addEventListener('click', function () {
console.log('我是儿子');
}, true)
</script>
3.2 冒泡阶段
被点击的是儿子,那么依据事件流,有个冒泡阶段,只要包裹儿子的爸爸绑定了点击事件,那么爸爸上面的事件也会执行。爷爷的事件也是如此。
<div id="grandpa">
父亲
<div id="father">
爸爸
<div id="son">
儿子
</div>
</div>
</div>
<script>
let grandpa = document.getElementById('grandpa');
let father = document.getElementById('father');
let son = document.getElementById('son');
grandpa.addEventListener('click', function () {
console.log('我是爷爷');
})
father.addEventListener('click', function () {
console.log('我是爸爸');
})
son.addEventListener('click', function () {
console.log('我是儿子');
})
</script>
3.3 阻止冒泡
有个时候我们并不想包裹儿子的爸爸也触发事件,此时我们需要阻止冒泡事件,通过event.stopPropagation()语句阻止冒泡事件
<div id="grandpa">
父亲
<div id="father">
爸爸
<div id="son">
儿子
</div>
</div>
</div>
<script>
let grandpa = document.getElementById('grandpa');
let father = document.getElementById('father');
let son = document.getElementById('son');
grandpa.addEventListener('click', function () {
console.log('我是爷爷');
})
father.addEventListener('click', function () {
console.log('我是爸爸');
})
son.addEventListener('click', function () {
console.log('我是儿子');
// 阻止冒泡
event.stopPropagation();
})
</script>
3.4 event.target和this在事件处理程序中区别
- event.target是实际被点击的元素
- this是绑定事件的元素
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
// 1. 获取ul元素
var ul = document.querySelector('ul');
// 2. 给ul添加点击事件
ul.addEventListener('click',function(){
// 3. 在事件处理程序中打印,当前点击的元素的内容
console.log(event.target.innerHTML);
console.log(this);
})
</script>
3.5 事件委托
事件委托利用的就是事件流中的冒泡原理,将事件委托到父元素上,用event.target获取被点击的元素,拿到被点击元素就执行回调函数
<!--
1.创建一个ul,内部有10个li元素,点击li元素的时候打印‘点击了li’
-->
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
</ul>
<script>
var ul = document.querySelector('ul');
//事件绑定到父元素ul上,通过event.target拿到被点击的元素
ul.addEventListener('click',function(){
console.log(event.target.innerHTML);
})
</script>
3.6 移动端点击事件的问题
移动端点击事件延迟300ms
移动端浏览器会有一些默认的行为,比如双击缩放、双击滚动。
这些行为,尤其是双击缩放,主要是为桌面网站在移动端的浏览体验设计的。
而在用户对页面进行操作的时候,移动端浏览器会优先判断用户是否要触发默认的行为
用户在 iOS Safari 里边点击了一个链接。由于用户可以进行双击缩放或者双击滚动的操作,
当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。
因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕。 鉴于iPhone的成功,
其他移动浏览器都复制了 iPhone Safari 浏览器的多数约定,包括双击缩放,
几乎现在所有的移动端浏览器都有这个功能
移动端点击事件穿透
起因:事件执行的顺序是touchstart > touchend > click
页面上有两个元素A和B。B元素在A元素之上。我们在B元素的touchstart事件上注册了一个回调函数, 该回调函数的作用是隐藏B元素。我们发现,当我们点击B元素,B元素被隐藏了,随后,A元素触发了click事件
解决方法
使用fastclick.js解决,都可以用fastclick解决掉
fastclick 原理:
在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,
并把浏览器在300ms之后真正的click事件阻止掉
使用方式: 在main.js中安装,引入,改造全局的点击事件
<div style='width: 400px;height: 400px;background-color: blue;position: relative;'>
<div class='inner' style='width: 200px;height: 200px;background-color: blueviolet;'></div>
<div class='inner'
style='width: 200px;height: 200px;background-color: yellow;position: absolute;left: 50px;top: 50px;'></div>
</div>
//引入fastclick.js
<script src="./fastclick.js"></script>
<script>
var wrap = document.querySelector('div');
var inner1 = document.querySelectorAll('.inner')[0];
var inner2 = document.querySelectorAll('.inner')[1];
//解决点击穿透和延迟300ms
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function () {
FastClick.attach(document.body);
}, false);
}
inner2.addEventListener('touchend', function () {
wrap.removeChild(this);
})
inner1.addEventListener('click', function () {
console.log(1);
})
</script>