目录
- [Web APIs - 第3天](#Web APIs - 第3天)
Web APIs - 第3天
进一步学习 事件进阶,实现更多交互的网页特效,结合事件流的特征优化事件执行的效率
- 掌握阻止事件冒泡的方法
- 理解事件委托的实现原理
全选文本框案例
html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
* {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #c0c0c0;
width: 500px;
margin: 100px auto;
text-align: center;
}
th {
background-color: #09c;
font: bold 16px "微软雅黑";
color: #fff;
height: 24px;
}
td {
border: 1px solid #d0d0d0;
color: #404060;
padding: 10px;
}
.allCheck {
width: 80px;
}
</style>
</head>
<body>
<table>
<tr>
<th class="allCheck">
<input type="checkbox" name="" id="checkAll"> <span class="all">全选</span>
</th>
<th>商品</th>
<th>商家</th>
<th>价格</th>
</tr>
<tr>
<td>
<input type="checkbox" name="check" class="ck">
</td>
<td>小米手机</td>
<td>小米</td>
<td>¥1999</td>
</tr>
<tr>
<td>
<input type="checkbox" name="check" class="ck">
</td>
<td>小米净水器</td>
<td>小米</td>
<td>¥4999</td>
</tr>
<tr>
<td>
<input type="checkbox" name="check" class="ck">
</td>
<td>小米电视</td>
<td>小米</td>
<td>¥5999</td>
</tr>
</table>
<script>
// 1. 获取大复选框
const checkAll = document.querySelector('#checkAll')
// 2. 获取所有的小复选框
const cks = document.querySelectorAll('.ck')
// 3. 点击大复选框 注册事件
checkAll.addEventListener('click', function () {
// 得到当前大复选框的选中状态
// console.log(checkAll.checked) // 得到 是 true 或者是 false
// 4. 遍历所有的小复选框 让小复选框的checked = 大复选框的 checked
for (let i = 0; i < cks.length; i++) {
cks[i].checked = this.checked
}
})
// 5. 小复选框控制大复选框
for (let i = 0; i < cks.length; i++) {
// 5.1 给所有的小复选框添加点击事件
cks[i].addEventListener('click', function () {
// 判断选中的小复选框个数 是不是等于 总的小复选框个数
// 一定要写到点击里面,因为每次要获得最新的个数
// console.log(document.querySelectorAll('.ck:checked').length)
// console.log(document.querySelectorAll('.ck:checked').length === cks.length)
checkAll.checked = document.querySelectorAll('.ck:checked').length === cks.length
})
}
</script>
</body>
</html>
事件流
事件流是对事件执行过程的描述,了解事件的执行过程有助于加深对事件的理解,提升开发实践中对事件运用的灵活度。
如上图所示,任意事件被触发时总会经历两个阶段:【捕获阶段】和【冒泡阶段】。
简言之,捕获阶段是【从父到子】的传导过程,冒泡阶段是【从子向父】的传导过程。
实际工作都是使用事件冒泡为主
事件捕获
html
<!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>
.father {
width: 500px;
height: 500px;
background-color: pink;
}
.son {
width: 200px;
height: 200px;
background-color: purple;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
const fa = document.querySelector('.father')
const son = document.querySelector('.son')
// 山东 济南 蓝翔 目标(pink老师) 捕获阶段
// 蓝翔 济南 山东 冒泡阶段
document.addEventListener('click', function () {
alert('我是爷爷')
}, true)
fa.addEventListener('click', function () {
alert('我是爸爸')
}, true)
son.addEventListener('click', function () {
alert('我是儿子')
}, true)
</script>
</body>
</html>
事件冒泡
html
<!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>
.father {
width: 500px;
height: 500px;
background-color: pink;
}
.son {
width: 200px;
height: 200px;
background-color: purple;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
const fa = document.querySelector('.father')
const son = document.querySelector('.son')
// 山东 济南 蓝翔 目标(pink老师) 捕获阶段
// 蓝翔 济南 山东 冒泡阶段
document.addEventListener('click', function () {
alert('我是爷爷')
})
fa.addEventListener('click', function () {
alert('我是爸爸')
})
son.addEventListener('click', function (e) {
alert('我是儿子')
// 组织流动传播 事件对象.stopPropagation()
e.stopPropagation()
})
</script>
</body>
</html>
阻止冒泡
阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。
总结:
1.阻止冒泡如何做?
事件对象.stopPropagation()
2.阻止元素默认行为如何做?
e.preventDefault()
解绑事件
on事件方式解绑
addEventListener方式解绑
注意事项-鼠标经过事件的区别
鼠标经过事件:
mouseover 和 mouseout 会有冒泡效果
mouseenter 和 mouseleave 没有冒泡效果 (推荐)
两种注册事件的区别
-
传统on注册(L0)
- 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
- 直接使用null覆盖偶就可以实现事件的解绑
- 都是冒泡阶段执行的
-
事件监听注册(L2)
- 语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
- 后面注册的事件不会覆盖前面注册的事件(同一个事件)
- 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
- 必须使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)
- 匿名函数无法被解绑
事件委托
综合案例-tab栏切换改造
html
<!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>
// 采取事件委托的形式 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) // e.target是我们点击的对象
// 我们只有点击了 a 才会 进行 添加类和删除类操作
// console.log(e.target.tagName) // e.target.tagName 点击那个对象的 标签名
if (e.target.tagName === 'A') {
// console.log('我选的是a')
// 排他思想 ,先移除原来的active
document.querySelector('.tab-nav .active').classList.remove('active')
//当前元素添加 active 是 e.target
// this 指向ul 不能用this
e.target.classList.add('active')
// 下面大盒子模块
// console.log(e.target.dataset.id)
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')
}
})
</script>
</body>
</html>
其他事件
页面加载事件
总结:
- 页面加载事件有哪两个?如何添加?
load 事件
- 监听整个页面资源给
window
加DOMContentLoaded
给
document
加无需等待样式表、图像等完全加载
元素滚动事件
页面滚动事件-获取位置
页面滚动事件-滚动到指定的坐标
页面尺寸事件
页面尺寸事件-获取元素宽高
元素尺寸与位置
-
offsetLeft和offsetTop 注意是只读属性
- 获取元素距离自己定位父级元素的左、上距离
-
element.getBoundingClientRect()
- 方法返回元素的大小及其相对于视口的位置
注意:
offsetWidth和offsetHeight是得到元素什么的宽高?
- 内容 + padding + border
offsetTop和offsetLeft 得到位置以谁为准?
带有定位的父级
如果都没有则以 文档左上角 为准
综合案例-实现bilibili 点击小滑块移动效果
javascript
<script>
// 1. 事件委托的方法 获取父元素 tabs-list
const list = document.querySelector('.tabs-list')
const line = document.querySelector('.line')
// 2. 注册点击事件
list.addEventListener('click', function (e) {
// 只有点击了A 才有触发效果
if (e.target.tagName === 'A') {
// console.log(11)
// 当前元素是谁 ? e.target
// 得到当前点击元素的位置
// console.log(e.target.offsetLeft)
// line.style.transform = 'translateX(100px)'
// 把我们点击的a链接盒子的位置 然后移动
line.style.transform = `translateX(${e.target.offsetLeft}px)`
}
})
</script>
综合案例-电梯导航
需求:点击不同的模块,页面可以自动跳转不同的位置
模块分析:
①:页面滚动到对应位置,导航显示,否则隐藏模块
②:点击导航对应小模块,页面 会跳到对应大模块位置
③:页面滚动到对应位置,电梯导航对应模块自动发生变化
javascript
<script>
// 第一大模块,页面滑动可以显示和隐藏
(function () {
// 获取元素
const entry = document.querySelector('.xtx_entry')
const elevator = document.querySelector('.xtx-elevator')
// 1. 当页面滚动大于 300像素,就显示 电梯导航
// 2. 给页面添加滚动事件
window.addEventListener('scroll', function () {
// 被卷去的头部大于 300
const n = document.documentElement.scrollTop
// if (n >= 300) {
// elevator.style.opacity = 1
// } else {
// elevator.style.opacity = 0
// }
// 简写
elevator.style.opacity = n >= entry.offsetTop ? 1 : 0
})
// 点击返回页面顶部
const backTop = document.querySelector('#backTop')
backTop.addEventListener('click', function () {
// 可读写
// document.documentElement.scrollTop = 0
// window.scrollTo(x, y)
window.scrollTo(0, 0)
})
})();
// 第二第三都放到另外一个执行函数里面
(function () {
// 2. 点击页面可以滑动
const list = document.querySelector('.xtx-elevator-list')
list.addEventListener('click', function (e) {
// console.log(11)
if (e.target.tagName === 'A' && e.target.dataset.name) {
// 排他思想
// 先移除原来的类active
// 先获取这个active的对象
const old = document.querySelector('.xtx-elevator-list .active')
// console.log(old)
// 判断 如果原来有active类的对象,就移除类,如果开始就没有对象,就不删除,所以不报错
if (old) old.classList.remove('active')
// 当前元素添加 active
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的对象
const old = document.querySelector('.xtx-elevator-list .active')
// console.log(old)
// 判断 如果原来有active类的对象,就移除类,如果开始就没有对象,就不删除,所以不报错
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) {
// 选择第一个小盒子
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>