目录
1.事件流
1.1 事件流和两个阶段说明
1.2 事件捕获
1.3 事件冒泡
1.4 阻止冒泡
1.5 解除默认行为
1.6 解绑事件
2.事件委托
3.其他事件
3.1 页面加载事件
3.1.1 load方式
3.1.2 DOMContentLoaded
3.2 元素滚动事件
3.2.1 什么是元素滚动事件
3.2.2 获取元素滚动的位置
3.2.3 页面尺寸事件
4.元素尺寸与位置
4.1 概念
4.2 案例
4.3 总结
5.综合案例
1.事件流
1.1 事件流和两个阶段说明
事件流指的是事件完整执行过程中的流动路径。
说明:
- 假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段
- 捕获阶段是 从父到子 冒泡阶段是从子到父
- 实际工作都是使用事件冒泡为主
1.2 事件捕获
事件捕获概念: 多一句没有,少一句不行,用最短时间,教会最实用的技术!
从DOM的根元素开始去执行对应的事件 (从外到里)
事件捕获需要写对应代码才能看到效果
语法:
说明:
- Ø addEventListener第三个参数传入 true 代表是捕获阶段触发(很少使用)
- Ø 若传入false代表冒泡阶段触发,默认就是false
- Ø 若是用 L0 事件监听,则只有冒泡阶段,没有捕获
1.3 事件冒泡
事件冒泡:当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。
这一过程被称为事件冒 泡。
简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的 同名事件
事件冒泡是默认存在的
L2事件监听第三个参数是 false,或者默认都是冒泡
1.4 阻止冒泡
问题:因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素
需求:若想把事件就限制在当前元素内,就需要阻止事件冒泡
前提:阻止事件冒泡需要拿到事件对象
语法:
注意:此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效。
1.5 解除默认行为
我们某些情况下需要阻止默认行为的发生,比如 阻止 链接的跳转,表单域跳转。
语法:
1.6 解绑事件
on事件方式,直接使用null覆盖偶就可以实现事件的解绑。
语法:
addEventListener事件方式,必须使用
removeEventListener(事件类型, 事件处理函数, [获取捕获或者冒泡阶段])
注意:匿名函数无法被解绑
两种注册事件的区别
传统on注册(L0)
- Ø 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
- Ø 直接使用null覆盖偶就可以实现事件的解绑
- Ø 都是冒泡阶段执行的
事件监听注册(L2)
- Ø 语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
- Ø 后面注册的事件不会覆盖前面注册的事件(同一个事件)
- Ø 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
- Ø 必须使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)
- Ø 匿名函数无法被解绑
2.事件委托
问题:像下面这张图,我们以前都是循环来给 li 注册事件,但是很麻烦。
改进方案:
事件委托是利用事件流的特征解决一些开发需求的知识技巧
- Ø 优点:减少注册次数,可以提高程序性能
- Ø 原理:事件委托其实是利用事件冒泡的特点。
- Ø 给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事 件
- Ø 实现:事件对象.target. tagName 可以获得真正触发事件的元素 ul.addEventListener('click', function(){}) 执行父级点击事件
案例:
tab栏切换改造
需求:优化程序,将tab切换案例改为事件委托写法
效果:
思路:
- ①:给a的父级 注册点击事件,采取事件委托方式
- ②: 如果点击的是A , 则进行排他思想,删除添加类
- ③: 注意判断的方式 利用 e.target.tagName
- ④: 因为没有索引号了,所以这里我们可以自定义属性,给5个链接添加序号
- ⑤: 下面大盒子获取索引号的方式 e.target.dataset.id 号, 然后进行排他思想
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>tab栏切换</title>
<style>
* {
margin: 0;
padding: 0;
}
.tab {
width: 590px;
height: 340px;
margin: 20px;
border: 1px solid #e4e4e4;
}
.tab-nav {
width: 100%;
height: 60px;
line-height: 60px;
display: flex;
justify-content: space-between;
}
.tab-nav h3 {
font-size: 24px;
font-weight: normal;
margin-left: 20px;
}
.tab-nav ul {
list-style: none;
display: flex;
justify-content: flex-end;
}
.tab-nav ul li {
margin: 0 20px;
font-size: 14px;
}
.tab-nav ul li a {
text-decoration: none;
border-bottom: 2px solid transparent;
color: #333;
}
.tab-nav ul li a.active {
border-color: #e1251b;
color: #e1251b;
}
.tab-content {
padding: 0 16px;
}
.tab-content .item {
display: none;
}
.tab-content .item.active {
display: block;
}
</style>
</head>
<body>
<div class="tab">
<div class="tab-nav">
<h3>每日特价</h3>
<ul>
<li><a class="active" href="javascript:;" data-id="0">精选</a></li>
<li><a href="javascript:;" data-id="1">美食</a></li>
<li><a href="javascript:;" data-id="2">百货</a></li>
<li><a href="javascript:;" data-id="3">个护</a></li>
<li><a href="javascript:;" data-id="4">预告</a></li>
</ul>
</div>
<div class="tab-content">
<div class="item active"><img src="./images/tab00.png" alt="" /></div>
<div class="item"><img src="./images/tab01.png" alt="" /></div>
<div class="item"><img src="./images/tab02.png" alt="" /></div>
<div class="item"><img src="./images/tab03.png" alt="" /></div>
<div class="item"><img src="./images/tab04.png" alt="" /></div>
</div>
</div>
<script>
//事件委托
const ul = document.querySelector('.tab-nav ul')
//注册点击事件
ul.addEventListener('click', function (e) {
//查看对象信息
// console.dir(e.target)
//如果对象为a标签就触发
if (e.target.tagName === 'A') {
//先移除,再添加
document.querySelector('.tab-nav .active').classList.remove('active')
//获取当前选择的对象,并添加类选择器
e.target.classList.add('active')
//获取当前对象的data-id自定义属性
//注意这里取过来是字符串类型的
const i = +e.target.dataset.id
//先移除,再添加
document.querySelector('.tab-content .active').classList.remove('active')
document.querySelector(`.tab-content .item:nth-child(${i + 1})`).classList.add('active')
}
})
</script>
</body>
</html>
3.其他事件
3.1 页面加载事件
3.1.1 load方式
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件 。
为什么要学?
- Ø 有些时候需要等页面资源全部处理完了做一些事情
- Ø 老代码喜欢把 script 写在 head 中,这时候直接找 dom 元素找不到
事件名:load
监听页面所有资源加载完毕给 window 添加 load 事件
注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件。
3.1.2 DOMContentLoaded
当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像等完 全加载。
事件名:DOMContentLoaded
监听页面DOM加载完毕: 给 document 添加 DOMContentLoaded 事件
3.2 元素滚动事件
3.2.1 什么是元素滚动事件
滚动条在滚动的时候持续触发的事件
为什么要学?
很多网页需要检测用户把页面滚动到某个区域后做一些处理, 比如固定导航栏,比如返回顶部。
事件名:scroll
监听整个页面滚动:给 window 或 document 添加 scroll 事件
使用场景:
我们想要页面滚动一段距离,比如100px,就让某些元素 ,显示隐藏,
那我们怎么知道,页面滚动了100像素呢?
3.2.2 获取元素滚动的位置
scrollLeft和scrollTop (属性)
- Ø 获取元素内容往左、往上滚出去看不到的距离
- Ø 这两个值是可读写的
尽量在scroll事件里面获取被卷去的距离
开发中,我们经常检测页面滚动的距离,比如页面滚动100像素,就可以显示一个元素,或者固定一个元素。
案例:
页面滚动显示隐藏侧边栏
需求:
- 1.当页面滚动大于300像素的距离时候,就显示侧边栏,否则隐藏侧边栏。
- 2.点击返回按钮,页面会返回顶部
分析:
- ①:需要用到页面滚动事件
- ②:检测页面被卷去的头部,如果大于300,就让侧边栏显示
- ③:显示和隐藏配合css过渡,利用opacity加渐变效果
代码:
<script>
//获取对象
const dt = document.querySelector('.xtx-elevator')
//获取顶部对象
const tops = document.querySelector('#backTop')
//添加页面滚动条事件
window.addEventListener('scroll', function () {
if (document.documentElement.scrollTop >= 300) {
dt.style.opacity = 1
} else {
dt.style.opacity = 0
}
})
//添加点击事件
tops.addEventListener('click', function () {
document.documentElement.scrollTop = 0
})
</script>
3.2.3 页面尺寸事件
事件类型:resize
检测屏幕宽度:
获取宽高:
- Ø 获取元素的可见部分宽高(不包含边框,margin,滚动条等)
- Ø clientWidth和clientHeight
4.元素尺寸与位置
4.1 概念
使用场景:
- Ø 前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个元素,就可以做某些事。
- Ø 简单说,就是通过js的方式,得到元素在页面中的位置
- Ø 这样我们可以做,页面滚动到这个位置,就可以做某些操作,省去计算了
获取宽高:
- Ø 获取元素的自身宽高、包含元素自身设置的宽高、padding、border
- Ø offsetWidth和offsetHeight
- Ø 获取出来的是数值,方便计算
- Ø 注意: 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0
获取位置:
- Ø 获取元素距离自己定位父级元素的左、上距离
- Ø offsetLeft和offsetTop 注意是只读属性
4.2 案例
仿京东固定导航栏案例
需求:当页面滚动到秒杀模块,导航栏自动滑入,否则滑出
分析:
①:用到页面滚动事件
②:检测页面滚动大于等于 秒杀模块的位置 则滑入,否则滑出
③:主要移动的是秒杀模块的顶部位置
效果:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.content {
overflow: hidden;
width: 1000px;
height: 3000px;
background-color: pink;
margin: 0 auto;
}
.backtop {
display: none;
width: 50px;
left: 50%;
margin: 0 0 0 505px;
position: fixed;
bottom: 60px;
z-index: 100;
}
.backtop a {
height: 50px;
width: 50px;
background: url(./images/bg2.png) 0 -600px no-repeat;
opacity: 0.35;
overflow: hidden;
display: block;
text-indent: -999em;
cursor: pointer;
}
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 80px;
background-color: purple;
text-align: center;
color: #fff;
line-height: 80px;
font-size: 30px;
transition: all .3s;
}
.sk {
width: 300px;
height: 300px;
background-color: skyblue;
margin-top: 500px;
}
</style>
</head>
<body>
<div class="header">我是顶部导航栏</div>
<div class="content">
<div class="sk">秒杀模块</div>
</div>
<div class="backtop">
<img src="./images/close2.png" alt="">
<a href="javascript:;"></a>
</div>
<script>
//获取对象
const hd = document.querySelector('.header')
const sk = document.querySelector('.content .sk')
//添加页面滚动事件
window.addEventListener('scroll', function () {
//不断获取页面滚动条的位置信息
const n = document.documentElement.scrollTop
if (n >= sk.offsetTop) {
//不显示导航栏
hd.style.top = 0
} else {
//这里一定要加单位
hd.style.top = '-80px'
}
})
</script>
</body>
</html>
4.3 总结
5.综合案例
电梯导航
效果:
需求:点击不同的模块,页面可以自动跳转不同的位置
模块分析:
- ①:页面滚动到对应位置,导航显示,否则隐藏模块
- 显示隐藏电梯盒子和返回顶部已经完成,可以放到自执行函数里面,防止变量污染
- ②:点击导航对应小模块,页面 会跳到对应大模块位置
- 字体高亮效果:
- ①:点击小模块,当前添加 active这个类
- ②:解决处理初次获取不到active 报错的问题
- 解决方案:
- ①: 不能直接获取这个类,然后移除,这样会报错
- ②: 先获取这个类,然后加个判断- 如果有这个类,就移除- 如果么有这个类,返回为 null, 就不执行移除,就不报错了
- 小模块跳转大模块:
- 核心思想: 把对应大盒子的offfsetTop 给document.documentElement.scrollTop
- ①: 我们发现小盒子li 的自定义属性里面值 跟 大盒子 后面一致
- ②: 利用模板字符串 把点击的 自定义属性值 给 大盒子,就找到对应的大盒子了
- ③: 然后拿到这个大盒子的 offsetTop 值 给 document.documentElement.scrollTop 即可
- ③:页面滚动到大盒子位置,电梯导航小盒子对应模块自动处于选中状态
- ①: 当页面滚动了,先移除所有小li的状态
- ②: 因为页面滚动需要不断获取大盒子的位置,所以需要把所有的大盒子都获取过来
- ③: 开始进行滚动判断- 如果页面滚动大于 新鲜好物大盒子的offsetTop 并且小于 人气推荐盒子的 offsetTop 就把 对应的小盒子先出来添加类- 依次类推- 最后一个,如果大于等于最新专题模块, 就选出最后一个对应小盒子(更精确)
代码:
<script>
/* 第一个模块 */
(function () {
//获取对象
const dt = document.querySelector('.xtx-elevator')
//获取顶部对象
const tops = document.querySelector('#backTop')
//获取对象
const entry = document.querySelector('.xtx_entry')
//添加页面滚动条事件
window.addEventListener('scroll', function () {
//offsetTop用来获取盒子距离上方的高度
if (document.documentElement.scrollTop >= entry.offsetTop) {
dt.style.opacity = 1
} else {
dt.style.opacity = 0
}
})
//添加点击顶部事件
tops.addEventListener('click', function () {
document.documentElement.scrollTop = 0
})
})();
/* 第二个模块 */
(function () {
/*
①:页面滚动到对应位置,导航显示,否则隐藏模块
②:点击导航对应小模块,页面 会跳到对应大模块位置
③:页面滚动到对应位置,电梯导航对应模块自动发生变化 */
// 获取对象
const list = document.querySelector('.xtx-elevator-list')
/* 添加小盒子点击事件 */
list.addEventListener('click', function (e) {
//当对象为a时触发并且该对象有自定义属性
if (e.target.tagName === 'A' && e.target.dataset.name) {
//获取类名的对象,如果没有这个对象则返回null
const old = document.querySelector('.xtx-elevator-list .active')
//排他思想,这里加if是没有默认的active
if (old) {
old.classList.remove('active')
}
//当前对象添加类名
e.target.classList.add('active')
/* 点击小盒子滚动到大盒子 */
document.documentElement.scrollTop =
document.querySelector(`.xtx_goods_${e.target.dataset.name}`).offsetTop
}
})
/* 大盒子找小盒子 */
window.addEventListener('scroll', function () {
//获取类名的对象,如果没有这个对象则返回null
const old = document.querySelector('.xtx-elevator-list .active')
//排他思想,这里加if是没有默认的active
if (old) {
old.classList.remove('active')
}
//获取四个大盒子的位置
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) {
//自定义选择器
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')
}
})
})();
</script>