1. DOM事件流
:::tips => 事件在执行过程中的流动路径,描述的是 元素在页面中接收事件的顺序 ::: 事件流的三个阶段
- 捕获阶段 (从外到内)
- 处于目标阶段
- 冒泡阶段 (从内到外)默认
- 我们的事件 ,在元素之间有一个传播的过程,事件在传播
- 默认情况下,事件是从内到外传播的,也就是从具体的元素,到外层元素,也叫事件冒泡
javascript
// dom.addEventListener(type, listener, useCapture)
// 1. type 事件类型
// 2. listener 回调函数,事件处理程序
// 3. useCapture 是否使用捕获机制 默认false ,使用事件冒泡
// 事件的流动路径,事件在传播,即使我们不监听这个事件,如果点击了某个元素,事件一样会发生
1.1 事件捕获
javascript
const father = document.querySelector('.father')
const son = document.querySelector('.son')
const body = document.body
const html = document.documentElement
// 事件捕获 :document => html => body => father => son
// 演示 事件捕获 ==> 从外到内
son.addEventListener('click', function(){
console.log('我是子盒子,我接收到了点击事件')
}, true)
html.addEventListener('click', function(){
console.log('html')
}, true)
father.addEventListener('click', function(){
console.log('我是父盒子,我接收到了点击事件')
}, true)
body.addEventListener('click', function(){
console.log('body')
}, true )
document.addEventListener('click', function(){
console.log('document')
}, true)
1.2 事件冒泡
:::tips 事件从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档) ---JS高程4 490 :::
javascript
// 体会一下事件冒泡:
// son => father => body => html => document
// addEventListener第三个参数,如果不传,或者写false,默认就是事件冒泡
const father = document.querySelector('.father')
const son = document.querySelector('.son')
const body = document.body
const html = document.documentElement
son.addEventListener('click', function(){
console.log('我是子盒子,我接收到了点击事件')
}, false)
html.addEventListener('click', function(){
console.log('html')
}, false)
father.addEventListener('click', function(){
console.log('我是父盒子,我接收到了点击事件')
}, false)
body.addEventListener('click', function(){
console.log('body')
}, false)
document.addEventListener('click', function(){
console.log('document')
}, false)
// 事件默认是以冒泡的方式传播的
// 事件的传播方式:要么是事件捕获,要么是事件冒泡 , => 第三个参数只能是true或者false(不写)
1.3 阻止事件冒泡和阻止默认行为
INFO
- **e.stopPropagation() **阻止事件冒泡
- e.preventDefault() 阻止默认行为
=> 注意:它们不会阻止代码的运行
1.4 鼠标经过、鼠标离开
- mouseenter / mouseleave => 没有事件冒泡
- mouseover / mouseout => 有事件冒泡的
2. 事件委托
事件委托:把事件委托给父元素,代为处理
DANGER
原理:利用事件冒泡,将事件监听绑定到父元素上,利用父元素来监听子元素的事件
:::tips 好处:减少注册事件的次数,提高程序的性能 :::
事件对象相关属性
WARNING
- e.target => 触发事件的元素
- e.target.tagName === ' 大写标签名' => 判断具体是哪个元素触发
- e.target.className => 判断是什么类名的元素
- this => 事件源,绑定事件的元素
2.1 Tab切换,事件委托版本
javascript
// 获取公共的父元素 ul
const ul = document.querySelector('.tab-nav ul')
// 获取所有的item盒子
const items = document.querySelectorAll('.item')
ul.addEventListener('mouseover', function(e){
// 只有经过了a标签,才触发以下的逻辑
if (e.target.tagName === 'A'){
// 排他思想
// 找到a标签中带有active类名的那个元素,移除
document.querySelector('.tab-nav .active').classList.remove('active')
// 让鼠标经过的那个a标签,高亮
e.target.classList.add('active')
console.log(e.target.dataset.id) // 0
// 下面盒子的显示
// 给5个a标签,添加自定义属性,标号序号 data-id
const i = e.target.dataset.id
document.querySelector('.tab-content .active').classList.remove('active')
items[i].classList.add('active')
} // end if
})
2.2 自定义属性
- 设置 data- 开头的属性
- 获取 用 dataset 获取
javascript
// h5新增的 是 以data-开头
// 获取data-自定义属性 ==> dataset对象
dom.dataset.xxx
dom.dataset['xxx']
// 属性的获取和设置、移除
// dom.getAttribute('属性名') 获取
// dom.setAttribute('属性名', 值) 设置属性
// dom.removeAttribute('属性名')
2.3 移除事件监听-事件解绑
- btn.onclick = null
- btn.removeEventListener('click', fn)
javascript
// DOM0
btn.onclick = function(){
// 如果要让事件处理程序执行一次,移除事件的代码要放在这儿
btn.onclick = null
}
// DOM2
const fn = function(){
console.log(666)
btn.removeEventListener('click', fn)
}
btn.addEventListener('click', fn)
4. 其他一些事件
4.1 页面加载事件
window => load document => DOMContentLoaded
javascript
// window ==> load
// => 页面上所有的资源加载完毕后,才会执行回调函数
window.addEventListener('load', function(){
})
// document ==> DOMContentLoaded
// => 当页面上的DOM元素加载完毕后,才会执行回调
document.addEventListener('DOMContentLoaded', function(){
})
4.2 页面滚动事件
window/ document => scroll
javascript
// 页面的滚动
window / document ==> scroll
window.addEventListener('scroll', function(){
})
// 获取元素被卷去的头部 左侧
dom.scrollTop
dom.scrollLeft
// 获取页面被卷去的头部
document.documentElement.scrollTop => 可读写
window.pageYOffset ==> 只读
// 滚动方法
=> window.scrollTo(x,y) / window.scrollTo(options)
=> window.scroll(x, y) / window.scroll()
window.scroll({
top: 0,
behavior: 'smooth'
})
// CSS的方式 平滑滚动
/* 让页面丝滑的滚动 */
html {
/* 平滑的滚动 */
/* scroll-behavior: smooth; */
}
5. 获取元素的尺寸-位置
5.1 元素的大小
clientWidth = width + padding offsetWidth = width + padding + border
javascript
// 1. clientWidth / clientHeight
clientWidth = width + padding => 它不包含border
// ==> flexiable 中 设置1rem的单元
function setRemUnit() {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
// 2. offsetWidth / offsetHeight
offsetWidth = width + padding + border
5.2. 元素的位置
son.offsetTop => 距离带有定位父元素的位置 (坐标) son.offsetLeft
javascript
// 元素的位置 offsetLeft / offsetTop 需要判断父元素有没有定位
// 1. 如果父元素带有定位, 以父元素为准
// console.log(son.offsetLeft)
// console.log(son.offsetTop)
// 2. 如果所有的父元素都没有定位,以浏览器文档为准
console.log(son.offsetTop)
console.log(son.offsetLeft)
6. 案例相关
6.1 导航吸附效果
javascript
const elevator = document.querySelector('.xtx-elevator')
const entry = document.querySelector('.xtx_entry')
// todo 01 吸附效果,两个元素的获取
const header = document.querySelector('.xtx_header')
const sticky = document.querySelector('.app-header-sticky')
// 监听页面滚动事件
window.addEventListener('scroll', function(){
// 实时获取被卷去的头部距离
const n = window.pageYOffset
elevator.style.opacity = n >= entry.offsetTop ? 1 : 0
// => todo02 吸附导航的效果
// 当滚动的位置大于header到顶部的距离,添加类 show
if (n >= header.offsetTop) {
sticky.classList.add('show')
} else {
sticky.classList.remove('show')
}
})
6.2 m-bilibili滑动
知识点:offsetLeft =>
- 元素相对于带有定位父元素的左侧位置。
- 如果没有定位父元素,左侧文档位置
javascript
// 1. 获取父元素 / 获取线 line
const box = document.querySelector('.tabs-list')
const line = document.querySelector('.line')
// 2. 事件委托的方式绑定 ,内部要判断点击的是a标签
box.addEventListener('click', function(e){
if(e.target.tagName === 'A'){
line.style.transform = `translateX(${e.target.offsetLeft}px)`
} // end if
})
6.3. e.target.className
- e.target.tagName 可以判断标签
- e.target.className 也可以判断点击的是哪一类名
javascript
<ul>
<li class="item">1</li>
<li class="item">2</li>
<li class="item">3</li>
<li class="item">4</li>
<li>5</li>
</ul>
javascript
// 需求:鼠标经过带有item类名的li标签,log它的内容
const ul = document.querySelector('ul')
// mouseover ==> 事件冒泡
ul.addEventListener('mouseover', function(e){
// => return 可以终止代码的运行,if语句如果只有一句,可以省略花括号,放后面
if (e.target.className !== 'item') return
// if (e.target.className === 'item'){
console.log(e.target.innerHTML)
// }
})
6.4 综合案例
todo01 点击右侧高亮的需求
知识点:
- 排他思想 => 找到带有active类名的元素,移出, 留下我自己
- this指向 ==> 事件源 , 给对应的small绑定的事件, this指向对应的哪个a标签
- 注意:一开始 没有active类名的元素,要判断是否存在
old && old.classList.remove('active')
javascript
// 电梯导航,点击small a标签,让对应的a标签高亮
const smalls = document.querySelectorAll('.small')
for(let i = 0; i < smalls.length; i++){
smalls[i].addEventListener('click', function(){
// 让点击的哪个高亮, ===> 排他思想
// 1. 找到带有active类名的small,移出,
// 因为一开始 没有 active类名的元素,所以报错Error
const old = document.querySelector('.xtx-elevator-list .active')
// if (old){
// old.classList.remove('active')
// }
// ==> 上面可以简写成一行
old && old.classList.remove('active')
// 2. 让当前点击的哪个a标签,添加active类名
this.classList.add('active')
})
}
todo02 点击小模块,页面滑动到对应大模块
知识点:
- offsetTop : 求大模块到顶部的距离 => bigs[i].offsetTop
- offsetHeight: sticky固定导航的高度
javascript
// 让页面滑动到对应的大模块,应为小模块small和big大模块一一对应,
// 我们可以直接通过下标取到达大模块
console.log(bigs[i])
document.documentElement.scrollTop = bigs[i].offsetTop - sticky.offsetHeight
javascript
const smalls = document.querySelectorAll('.small')
const bigs = document.querySelectorAll('.big')
for(let i = 0; i < smalls.length; i++){
smalls[i].addEventListener('click', function(){
// 让点击的哪个高亮, ===> 排他思想
// 1. 找到带有active类名的small,移出,
const old = document.querySelector('.xtx-elevator-list .active')
old && old.classList.remove('active')
// document.querySelector('.xtx-elevator-list .active').classList.remove('active')
// 2. 让当前点击的哪个a标签,添加active类名
this.classList.add('active')
// 3. 让页面滚动到大模块位置
document.documentElement.scrollTop = bigs[i].offsetTop - sticky.offsetHeight
})
}
todo03 页面滑动来控制小模块的高亮
- 逻辑代码要写在页面滚动的事件中, 当页面被卷去的头部 大于等于大模块到头部的距离 ,小模块高亮
n >= bigs[i].offsetTop
javascript
window.addEventListener('scroll', function(){
// 一开始可以把active类名先移除掉
const old = document.querySelector('.xtx-elevator-list .active')
old && old.classList.remove('active')
// 页面滚动到大模块,让对应的小模块高亮
for(let i = 0; i < bigs.length; i++){
// 当页面滚动的距离大于大模块到顶部的位置
if(n >= bigs[i].offsetTop - sticky.offsetHeight){
// 让对应的小模块高亮
const old = document.querySelector('.xtx-elevator-list .active')
old && old.classList.remove('active')
smalls[i].classList.add('active')
}
}
})