DOM事件进阶
事件流
事件流与两个阶段说明
概念
事件流:指的是事件完整执行过程中的流动路径
假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段
捕获阶段是 从父到子 冒泡阶段是从子到父
实际工作都是使用事件冒泡为主
事件捕获
概念
从DOM的根元素开始去执行对应的事件 (从外到里)
语法:事件源.addEventListener('事件方式',事件处理函数,是否使用捕获机制)
addEventListener第三个参数传入 true 代表是捕获阶段触发(很少使用)
若传入false代表冒泡阶段触发,默认就是false
若是用 L0 事件监听(on事件监听),则只有冒泡阶段,没有捕获
<div>
<p></p>
</div>
<script>
// 事件监听:事件源.addEventListener('事件方式',事件处理函数,是否使用捕获机制)
// 事件流:事件触发后的流程
// 捕获阶段 true
// 冒泡阶段 false 默认
const div = document.querySelector('div')
const p = document.querySelector('p')
div.addEventListener('click', function () {
console.log('div')
},true)
p.addEventListener('click', function () {
console.log('p')
},true)
</script>
事件冒泡
概念
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡
简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的 同名事件
L0事件监听,L2事件监听默认都是冒泡
<div>
<p></p>
</div>
<script>
const div = document.querySelector('div')
const p = document.querySelector('p')
div.addEventListener('click', function () {
console.log('div')
})
p.addEventListener('click', function () {
console.log('p')
})
</script>
阻止冒泡
阻止事件冒泡
因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素
若想把事件就限制在当前元素内,就需要阻止事件冒泡
阻止事件冒泡需要拿到事件对象
语法:事件对象.stopPropagation()
此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效
p.addEventListener('click', function (e) {
console.log('p')
// 阻止事件冒泡
e.stopPropagation()
})
阻止默认行为
我们某些情况下需要阻止默认行为的发生,比如 阻止 链接的跳转,表单域跳转
语法:事件对象.preventDefault()
<a href="http://www.baidu.com">链接</a>
<form action="http://www.baidu.com">
<input type="submit" value="提交">
</form>
<script>
const a = document.querySelector('a')
const form = document.querySelector('form')
a.addEventListener('click', function (e) {
// 阻止a默认跳转行为
// 事件对象.preventDefault() 阻止默认
e.preventDefault()
})
form.addEventListener('click', function (e) {
// 阻止表单默认提交行为
e.preventDefault()
})
</script>
解绑事件
传统on注册(L0)
绑定事件:事件源.onxxxx = function () {}
解绑事件: 事件源.onxxxx = null
- 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
- 直接使用null覆盖偶就可以实现事件的解绑
- 都是冒泡阶段执行的,无法开启捕获阶段
<button>点击</button>
<script>
// 绑定事件
btn.onclick = function () {
console.log('onclick1')
}
btn.onclick = function () {
console.log('onclick2')
}
// 解绑事件
btn.onclick = null
</script>
事件监听注册(L2)
绑定事件:事件源.addEventListener('事件类型', 处理函数)
解绑事件:事件源.removeEventListener('事件类型', 处理函数名)
- 语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
- 后面注册的事件不会覆盖前面注册的事件(同一个事件)
- 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
- 必须使用removeEventListener('事件类型', 事件处理函数名, 是否开启捕获阶段)
- 匿名函数无法被解绑
<button>点击</button>
<script>
const f1 = function () {
console.log('addEventListener1')
}
const f2 = function () {
console.log('addEventListener2')
}
// 绑定事件
// 匿名函数,无法解绑
btn.addEventListener('click', function () {
console.log('addEventListener0')
})
btn.addEventListener('click', f1)
btn.addEventListener('click', f2)
// 解绑事件
// 事件源.removeEventListener('事件类型', 事件处理程序的名称)
btn.removeEventListener('click', f2)
</script>
事件委托
概念
事件委托:把事件委托给上级元素,利用事件流的特征解决一些开发需求的知识技巧
优点:
减少注册次数,可以提高程序性能,动态创建的元素也可以有事件了
原理:
事件委托其实是利用事件冒泡的特点。 给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
实现:
事件对象.target:最先触发事件的元素 ,标签
事件对象.target. tagName :可以获得真正触发事件的元素 ,返回大写的标签名
<ul>
<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>
<li>10
<a href="">链接</a>
</li>
</ul>
<script>
// 事件委托:把事件委托给上级元素
/*
步骤:
1.给上级元素注册事件
2.利用事件对象.target找到最先触发元素
3.用tagName查看是否是我们要找的元素
*/
const ul = document.querySelector('ul')
// 注册事件
ul.addEventListener('click', function (e) {
// e.target有可能不是我们要找的元素,只有我们要找的元素才可以
if (e.target.tagName === 'LI') {
e.target.style.background = 'pink'
}
})
</script>
事件委托步骤:
1.给上级元素注册事件
2.利用事件对象.target找到最先触发元素
3.用tagName查看是否是我们要找的元素
其他事件
页面加载事件
load
事件名:load
触发方式:加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件
使用场景:监听页面所有资源加载完毕: 给 window 添加 load 事件
<script>
// 加载事件:load
// 当外部资源加载完成后触发的事件
// 只要涉及带有路径的,都涉及加载
// load:所有资源加载 绑定在window
window.addEventListener('load', function () {
console.log('页面所有资源加载完成后执行')
const img = document.querySelector('img')
console.log(img)
})
</script>
<img src="./images/b01.jpg" alt="">
</body>
注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件
<img src="./images/b01.jpg" alt="">
<script>
// 加载事件:load
// 当外部资源加载完成后触发的事件
// 只要涉及带有路径的,都涉及加载
const img = document.querySelector('img')
img.addEventListener('load', function () {
console.log('图片加载完成后执行')
})
</script>
</body>
DOMContentLoaded
事件名:DOMContentLoaded
触发方式:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像等完全加载
使用场景: 监听页面DOM加载完毕: 给 document 添加 DOMContentLoaded 事件
<script>
// DOM加载:当初始的HTML文档加载 绑定在document
document.addEventListener('DOMContentLoaded', function () {
console.log('当初始的HTML文档被完全加载和解析完成之后执行')
const img = document.querySelector('img')
console.log(img)
})
</script>
<img src="./images/b01.jpg" alt="">
</body>
滚动事件
绑定/添加滚动事件
事件名:scroll
触发方式:滚动条在滚动的时候持续触发的事件
使用场景: 谁有滚动条,给谁加滚动事件
- 监听某个元素的内部滚动直接给某个元素加即可
- 监听整个页面滚动: 给 window 或 document 添加 scroll 事件
<style>
div {
width: 200px;
height: 600px;
background-color: #eee;
overflow-y: scroll;
}
body {
height: 2000px;
}
</style>
<script>
// scroll:滚动事件 谁有滚动条,给谁加滚动事件
const div = document.querySelector('div')
div.addEventListener('scroll', function () {
console.log(`向上滚动${div.scrollTop}px`)
})
// 如果整个页面有滚动条,加给widow
window.addEventListener('scroll', function () {
console.log('滚动ing...')
})
</script>
获取位置
scrollLeft和scrollTop (属性)
使用场景:
- 在scroll事件里面获取被卷去的距离
- 获取被卷出的大小, 获取元素内容往左、往上滚出去看不到的距离
这两个值是可读写的,不仅可以获取,还可以设置
<script>
const div = document.querySelector('div')
div.addEventListener('scroll', function () {
console.log(`向上滚动${div.scrollTop}px`)
})
window.addEventListener('scroll', function () {
// 整个页面再往出卷,那么卷的是html的scrollTop
// 获取html
// const html= document.documentElement
const html = document.querySelector('html')
console.log(`向上滚动${html.scrollTop}px`)
})
// scrollTop不仅可以获取,还可以设置
const btn = document.querySelector('input')
btn.addEventListener('click', function () {
// 返回顶部,设置上卷距离为0
document.documentElement.scrollTop = 0
})
</script>
滚动到指定的坐标
语法:元素.scrollTo(x, y)
使用场景:把内容滚动到指定的坐标
btn.addEventListener('click', function () {
// 返回顶部,设置上卷距离为0
// document.documentElement.scrollTop = 0
window.scrollTo(0,0)
})
页面尺寸事件
绑定/添加滚动事件
事件名:resize
触发方式:窗口尺寸改变的时候触发事件
使用场景: 检测屏幕宽度: 给 window 添加 resize 事件
window.addEventListener('resize', function () {
const w = document.documentElement.clientWidth
console.log(`当前页面宽度:${w}`)
})
元素尺寸与位置
获取元素宽高
属性名:clientWidth和clientHeight
使用场景:获取元素的可见部分宽高:内容 + padding(不包含边框,margin,滚动条等)
属性名:offsetWidth和offsetHeight
使用场景:获取元素的自身宽高,包含元素自身设置的宽高、padding、border
注意
- 获取出来的是数值,方便计算
- 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0
- 以上两种属性只能获取,不能设置
- 获取页面宽高,通常使用clientWidth和clientHeight,获取元素宽高,通常使用:offsetWidth和offsetHeight
<style>
div {
width: 200px;
height: 200px;
padding: 10px;
margin: 10px;
border: 10px solid #000;
}
</style>
<div></div>
<script>
const div = document.querySelector('div')
div.style.width
// 获取宽高
// 获取页面宽高使用clientWidth
// clientWidth:包含:内容宽 + padding
console.log(div.clientWidth,div.clientHeight)
// offsetWidth:包含:内容宽 + padding + border
// 获取元素宽高:offsetWidth
console.log(div.offsetWidth,div.offsetHeight)
</script>
获取位置
属性名:offsetLeft和offsetTop
使用场景:获取元素距离自己定位父级元素的左、上距离,参照定位元素的位置距离
方法名:getBoundingClientRect
语法:element.getBoundingClientRect()
返回值:元素的大小及其相对于视口的位置
注意:
- offsetLeft和offsetTop 注意是只读属性,只能获取,不能设置
- offsetLeft和offsetTop 以带有定位的父级为参考坐标,如果上级中都没有定位则以文档左上角 为准