事件流指的是事件完整执行过程中的流动路径,简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父。
实际工作都是使用事件冒泡为主
事件捕获:
从DOM的根元素开始去执行对应的事件(从外到里)
DOM.addEventListener(事件类型,事件处理函数,是否使用捕获机制
)
addEventListener
第三个参数传入true
代表是捕获阶段触发(很少使用)若是用LO事件监听,则只有冒泡阶段,没有捕获
事件流冒泡
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡。
事件冒泡是默认存在的,L2事件监听第三个参数是 false
,或者默认都是冒泡。
简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件。
阻止冒泡
能够写出阻止冒泡的代码:事件对象.stopPropagation()
问题:因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素
需求:若想把事件就限制在当前元素内,就需要阻止事件冒泡
前提:阻止事件冒泡需要拿到事件对象
const fa = document.querySelector('.father')
const son = document.querySelector('.son')
document.addEventListener('click', function () {
alert('我是爷爷')
})
fa.addEventListener('click', function () {
alert('我是爸爸')
})
son.addEventListener('click', function (e) {
alert('我是儿子')
// 阻止流动传播
e.stopPropagation()
})
注意:此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效
阻止默认行为
我们某些情况下需要阻止默认行为的发生,比如 阻止链接的跳转,表单域跳转 Demo
const form = document.querySelector('form')
form.addEventListener('submit', function (e) {
// 阻止默认行为 阻止提交按钮
e.preventDefault()
})
const a = document.querySelector('a')
a.addEventListener('click', function (e) {
// 阻止默认行为 阻止跳转链接
e.preventDefault()
})
解绑事件
L0事件:on
事件方式,直接使用null覆盖偶就可以实现事件的解绑.
const btn = document.querySelector('button')
btn.onclick = function () {
alert('点击了')
// l0 事件移除解绑
btn.onclick = null
}
L2事件:匿名函数无法被解绑
const btn = document.querySelector('button')
// l2 事件移除解绑
function fn() {
alert('点击了')
// 移除解绑事件
btn.removeEventListener('click', fn)
}
// 绑定事件
btn.addEventListener('click', fn)
鼠标经过事件的区别
mouseover
和mouseout
会有冒泡效果
mouseenter
和mouselegve
沒有冒泡效果(推荐)
两种注册事件的区别
传统on注册(LO)
同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
直接使用null覆盖偶就可以实现事件的解绑
都是冒泡阶段执行的
事件监听注册(L2)
语法:addEventListener(事件类型,事件处理函数,是否使用捕获)
后面注册的事件不会覆盖前面注册的事件(同一个事件)
可以通过第三个参数去确定是在冒泡或者捕获阶段执行
必须使用removeEventListener(事件类型,事件处理函数,获取捕获或者冒泡阶段)
匿名函数无法被解绑
事件委托
事件委托是利用事件流的特征解决一些开发需求的知识技巧 Demo
优点:是减少注册次数,可以提高程序性能
原理:事件委托其实是利用事件冒泡的特点
其实:给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
实现:事件对象.target.tagName 可以获得真正触发事件的元素
// 点击每个小li,事件委托
// 1. 获得父元素,事件写到父级身上
const ul = document.querySelector('ul')
ul.addEventListener('click', function (e) {
// console.log(e) // 打印事件对象
// console.log(e.target) // 知道事件对象里面的目标target 是 li
// console.dir(e.target)// 事件对象里的target的标签tagName = LI
// 我们只要点击li才会有效果
if (e.target.tagName === 'LI') {
e.target.style.color = 'red'
}
})
案例:tab栏切换改造
需求:优化程序,将tab切换案例改为事件委托写法 Demo
// 采取事件委托的形式 tab拦切换
// 1. 获取ul父元素,因为ul只有一个
const ul = document.querySelector('.tab-nav ul')
// 获取 5个 item
const items = document.querySelectorAll('.tab-content .item')
// 2. 添加委托事件
ul.addEventListener('click', function (e) {
// console.log(e.target) // 打印事件点击的对象的目标属性
// console.log(e.target.tagName) // 打印事件对象的标签名
if (e.target.tagName === 'A') {
// 排查思想,先移除active
document.querySelector('.tab-nav .active').classList.remove('active')
// 当前元素添加active
e.target.classList.add('active')
}
// 下面大盒子模块
// console.log(e.target.dataset.id) // 0 1 2 3 4
// 获取小li的自定义属性
const i = +e.target.dataset.id
// 排查思想 ,先移除原来的active
document.querySelector('.tab-content .active').classList.remove('active')
// 对应的大盒子添加active
// document.querySelector(`.tab-content .item:nth-child(${i + 1})`).classList.add('active')
items[i].classList.add('active')
})
页面加事件
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件。
为什么要学?
有些时候需要等页面资源全部处理完了做一些事情
老代码喜欢把
script
写在head
中,这时候直接找dom
元素找不到
全部资源加载
<script>
// 等待页面所有资源加载完毕,就回去执行回调函数
window.addEventListener('load', function () {
const btn = document.querySelector('button')
btn.addEventListener('click', function () {
alert(11)
})
})
</script>
注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load
事件
const img = document.querySelector('img')
img.addEventListener('load', function () {
// 等待图片加载完毕再去执行
})
Dom文档加载
当初始的HTML 文档被完全加载和解析完成之后,DOMContentLoaded
事件被触发,而无需等待样式表、图像等完全加载
document.addEventListener('DOMContentLoaded', function () {
alert('我只等待html加载完毕')
})
元素滚动事件
滚动条在滚动的时候持续触发的事件。很多网页需要检测用户把页面滚动到某个区域后做一些处理,比如固定导航栏,比如返回顶部
监听整个页面滚动
// 页面滚动事件
window.addEventListener('scroll', function () {
console.log('我滚了')
})
使用场景:
我们想要页面滚动一段距离,比如
100px
,就让某些元素显示隐藏,那我们怎么知道,页面滚动了100像素呢?就可以使用
scroll
来检测滚动的距离~~~
滚动多少像素
获取被卷去的大小,获取元素内容往左、往上滚出去看不到的距离,这两个值是可读写的。
尽量在scroll
事件里面获取被卷去的距离
const div = document.querySelector('div')
div.addEventListener('scroll', function () {
// 被卷去的头部
console.log(div.scrollTop)
// 被卷去的左距
console.log(this.scrollLeft)
})
开发中,我们经常检测页面滚动的距离,比如页面滚动100像素,就可以显示一个元素,或者固定一个元素
const div = document.querySelector('div')
window.addEventListener('scroll', function () {
// 我想知道页面到底滚动了多少像素,被卷去了多少
// 获取html元素的写法 document.documentElement
// console.log(document.documentElement.scrollTop)
const n = document.documentElement.scrollTop
if (n >= 100) {
div.style.display = 'block'
} else {
div.style.display = 'none'
}
})
scroll
事件是可读写与赋值,
// 打开页面直接卷去800像素
document.documentElement.scrollTop = 800
案例:页面滚动显示隐藏侧边栏
需求:当页面滚动大于300像素的距离时候,就显示侧边栏,否则隐藏侧边栏 Demo
// 获取盒子元素
const elevator = document.querySelector('.xtx-elevator')
// 1. 当页面滚动大于300px就显示电梯导航
// 2. 给页面添加滚动事件
window.addEventListener('scroll', function () {
// 获取被卷去头部大于300
const n = document.documentElement.scrollTop
// if (n >= 300) {
// elevator.style.opacity = 1 // css淡入
// } else {
// elevator.style.opacity = 0 // css淡出
// }
elevator.style.opacity = n >= 300 ? 1 : 0 //三元运算符
})
// 点击返回页面顶部
const backTop = document.querySelector('#backTop')
backTop.addEventListener('click', function () {
// 可读写
// document.documentElement.scrollTop = 0
// 滚动到指定指标 scrollTo(x,y)坐标
window.scrollTo(0, 0)
})
change内容变化事件
表单内的内容发生了变化,才会触发这个事件。
const input = document.querySelector('input')
input.addEventListener('change', function () {
console.log(111)
})
页面尺寸事件
窗口尺寸改变的时候触发事件resize
,
// resize当浏览器窗口发生变化时触发的事件
window.addEventListener('resize', function () {
console.log(1)
})
获取元素的可见部分宽高(不包含边框,margin,滚动条等)
const div = document.querySelector('div')
console.log(div.clientWidth) // 这个盒子的宽度 200
console.log(div.clientHeight) // 这个盒子的高度
display: inline-block; // 转行内块样式
console.log(window.devicePixelRatio)// 移动端倍数
元素尺寸与位置
前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个元素,就可以做某些事。
下面通过js的方式,得到元素在页面中的位置,页面滚动到这个位置,就可以做某些操作,省去计算了。
获取宽高:
获取元素的自身宽高、包含元素自身设置的宽高、padding、border
获取出来的是数值,方便计算
注意:获取的是可视宽高,如果盒子是隐藏的,获取的结果是0
获取位置:Demo
获取元素距离自己定位父级元素的左、上距离
offsetLeft
和offsetTop
注意是只读属性
const div = document.querySelector('div')
const p = document.querySelector('p')
// 获取宽高
// 一个div盒子 200*200,获取盒子距离上边距和左边距的有多少像素
console.log(div.offsetLeft) // 108 body有8像素的外边距
console.log(div.offsetHeight) // 200
// 获取位置
// 检测盒子的位置 最近一级带有定位的祖先元素
console.log(p.offsetLeft) // 没定位 258,相当定位 50
console.log(p.offsetTop) // 没定位 100,相对定位 0
视口的位置
此方法返回元素的大小及其相对于视口的位置 Demo
const div = document.querySelector('div')
console.log(div.getBoundingClientRect())
案例:仿京东固定导航栏
需求:当页面滚动到秒杀模块,导航栏自动滑入,否则滑出 Demo
const sk = document.querySelector('.sk')
const headers = document.querySelector('.header')
// 1. 页面滚动事件
window.addEventListener('scroll', function () {
// 当页面滚动到 秒杀模块的时候,就改变 兴部的top值
// 页面被卷去的头部>= 秒杀模块的位置 offsetTop
const n = document.documentElement.scrollTop
// if (n >= sk.offsetTop) {
// headers.style.top = 0
// } else {
// headers.style.top = '-80px'
// }
headers.style.top = n >= sk.offsetTop ? 0 : '-80px'
})
案例:bilibili点击小滑块移动效果
需求:当点击链接,下面红色滑块跟着移动 Demo
分析:
① 用到事件委托
② 点击链接得到当前元素的 offsetLeft值
③ 修改line 颜色块的 transform 值 = 点击链接的offsetLeft
④: 添加过渡效果
// 1. 获取父元素 添加事件委托
const list = document.querySelector('.tabs-list')
const line = document.querySelector('.line')
// 2. 给a注册事件
list.addEventListener('click', function (e) {
// 判断点击的是a
if (e.target.tagName === 'A') {
// console.log(11)
// 得到当前链接的 offsetleft
// console.log(e.target.offsetLeft)
// 让 Line盒子来进行移动 ransform在原来的地方继续移动
line.style.transform = `translateX(${e.target.offsetLeft}px)` //1.25rem
}
})
案例:电梯导航
需求:点击不同的模块,页面可以自动跳转不同的位置 Demo
模块分析一:
① 页面滚动到对应位置,导航显示,否则隐藏模块,可以放到自执行函数里面,防止变量污染
② 点击导航对应小模块,页面 会跳到对应大模块位置
③ 页面滚动到对应位置,电梯导航对应模块自动发生变化
模块分析二:
点击每个模块,页面自动滚动到对应模块,使用事件委托方法更加简单
① 点击小模块,当前添加active
这个类
② 解决处理初次获取不到active
报错的问题解决方案:
① 不能直接获取这个类,然后移除,这样会报错
② 先获取这个类,然后加个判断如果有这个类,就移除,如果么有这个类,返回为
null
,就不执行移除,就不报错了
模块分析二点一:
点击不同的模块,页面可以自动跳转不同的位置
模块分析2:点击每个模块,页面自动滚动到对应模块,使用事件委托方法更加简单
点击小盒子li,页面跳转到对应大盒子位置
核心思想:
把对应大盒子的offfsetTop
给document.documentElement.scrollTop
① 我们发现小盒子li的自定义属性里面值 跟 大盒子 后面一致
② 利用模板字符串 把点击的 自定义属性值 给 大盒子,就找到对应的大盒子了,
③ 然后拿到这个大盒子的offsetTop
值给document.documentElement.scrollTop
即可
模块分析3:
点击不同的模块,页面可以自动跳转不同的位置
① 当页面滚动了,先移除所有小
li
的状态
② 因为页面滚动需要不断获取大盒子的位置,所以需要把所有的大盒子都获取过来
③ 开始进行滚动判断如果页面滚动大于 新鲜好物大盒子的
offsetTop
并且小于 人气推荐盒子的offsetTop
就把对应的小盒子先出来添加类依次类推,最后一个,如果大于等于最新专题模块,就选出最后一个对应小盒子(更精确)
让滚动条添加滚动效果
/* 页面滑动 */
html {
/* 让滚动条丝滑的滚动 */
scroll-behavior: smooth;
}
属性选择器
<input type="text" value="123" id="">
<input type="password" name="" id="">
/* input[value] {
color: red;
} */
input[type=text] {
color: red;
}
// 第一个大模块——滚动显示和返回顶部
// 套个立即执行函数
(function () {
// 获取盒子元素
const elevator = document.querySelector('.xtx-elevator')
// 1. 当页面滚动大于300px就显示电梯导航
// 2. 给页面添加滚动事件
window.addEventListener('scroll', function () {
// 获取被卷去头部大于300
const n = document.documentElement.scrollTop
// if (n >= 300) {
// elevator.style.opacity = 1 // css淡入
// } else {
// elevator.style.opacity = 0 // css淡出
// }
elevator.style.opacity = n >= 300 ? 1 : 0 //三元运算符
})
// 点击返回页面顶部
const backTop = document.querySelector('#backTop')
backTop.addEventListener('click', function () {
// 可读写
// document.documentElement.scrollTop = 0
window.scrollTo(0, 0)
})
})();
// 第二第三都放到另外一个执行函数里面
(function () {
const list = document.querySelector('.xtx-elevator-list')
list.addEventListener('click', function (e) {
// console.log(11)
// 判断事件对象里面的标签等于<a>, 逻辑运算符有自定义标签就执行代码
if (e.target.tagName === 'A' && e.target.dataset.name) {
// 排查思想,找不到active这个类,就赋值一个常量,返回的是null(未知)
const old = document.querySelector('.xtx-elevator-list .active')
// console.log(old) // null
// 判断:如果获取不到值,后面的代码就不执行了,不会报错;如果能获取到就删除类
if (old) old.classList.remove('active')
// 当前元素添加 cative
e.target.classList.add('active')
// 获得自定义属性 new topic
// console.log(e.target.dataset.name)
// 根据小盒子的自定义属性值 去选择 对应的大盒子
// 鼠标一点击就能获得自定属性,获取对应的大盒子的上边距
// console.log(document.querySelector(`.xtx_goods_${e.target.dataset.name}`).offsetTop)
// 获得对应大盒子的offsetTop
const top = document.querySelector(`.xtx_goods_${e.target.dataset.name}`).offsetTop
// 让页面滚动到对应的位置
document.documentElement.scrollTop = top
}
})
// 3. 项面滚动,可以根据大盒子选 小盒子 添加 active 类
window.addEventListener('scroll', function () {
// 3.1先移除类
// 排查思想,找不到active这个类,就赋值一个常量,返回的是null(未知)
const old = document.querySelector('.xtx-elevator-list .active')
// console.log(old) // null
// 判断:如果获取不到值,后面的代码就不执行了,不会报错;如果能获取到就删除类
if (old) old.classList.remove('active')
// 3.2 判断页面当前滑动的位置,选择小盒子
// 获取4个大盒子
const news = document.querySelector('.xtx_goods_new')
const popular = document.querySelector('.xtx_goods_popular')
const brand = document.querySelector('.xtx_goods_brand')
const topic = document.querySelector('.xtx_goods_topic')
const n = document.documentElement.scrollTop
if (n >= news.offsetTop && n < popular.offsetTop) {
//选择第一个小li 属性选择器
document.querySelector('[data-name = new]').classList.add('active')
} else if (n >= popular.offsetTop && n < brand.offsetTop) {
document.querySelector('[data-name = popular]').classList.add('active')
} else if (n >= brand.offsetTop && n < topic.offsetTop) {
document.querySelector('[data-name = brand]').classList.add('active')
} else if (n >= topic.offsetTop) {
document.querySelector('[data-name = topic]').classList.add('active')
}
})
})();
总结
属性 | 作用 | 说明 |
scrollLeft scrollTop | 被卷去的头部和左侧 | 配合页面滚动来用,可读写 |
clientwidth clientHeight | 获得元素宽度和高度 | 不包含border,margin 滚动条 用于js获取元素大小,只读属性 |
offsetWidth offsetHeight | 获得元素宽度和高度 | 包含border,padding ,滚动条等,只读 |
offsetLeft offsetTop | 获取元素距离自己定位父 级元素的左、上距离 | 获取元素位置的时候使用,只读属性 |
原创文章,作者:霍欣标,如若转载,请注明出处:https://www.bigengwu.cn/xue/117.html