事件流
事件流与两个阶段说明
事件流指的是事件完整执行过程中的流动路径(事件捕获=>事件目标阶段=>事件冒泡)
简单来说:捕获阶段是从父到子 冒泡阶段是从子到父
实际工作都是使用事件冒泡为主
事件捕获
概念:从DOM的根元素开始去执行对应的事件 (从外到里)
语法:DOM.addEventListener(事件类型,事件处理函数,是否使用捕获机制)
注意:
(1)addEventListener第三个参数传入 true 代表是捕获阶段触发(很少使用)
(2)若传入false代表冒泡阶段触发,默认就是false。
(3)若是用 L0 事件监听,则只有冒泡阶段,没有捕获。
(4)捕获机制必须使用事件监听的形式注册
事件冒泡
概念: 当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡。
- 当一个元素触发事件后,会依次向上调用所有父级元素的同名事件。(事件的类型一样)
- 事件冒泡是默认存在的
- L2事件监听第三个参数是 false,或者默认都是冒泡。
阻止冒泡
原因:因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素。
前提:阻止事件冒泡需要拿到事件对象
语法:事件对象.stopPropagation()
注意:此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效。
原因:阻止(元素)默认行为的发生,比如阻止链接的跳转,表单域跳转。
语法:e.preventDefault()
<form action="http://www.baidu.com">
<input type="submit" value="提交">
</form>
<script>
const form = document.querySelector('form')
form.addEventListener('click', function (e) {
//阻止表单默认提交行为
e.preventDefault()
})
</script>
解绑事件
on事件
//直接使用null覆盖偶就可以实现事件的解绑
//绑定事件
btn.onclick=function(){
alert('你好')
}
//解绑事件
btn.onclick = null
addEventListener方式
语法:removeEventListener(事件类型, 事件处理函数, [获取捕获或者冒泡阶段]) ([ ]里面的可以省略不写)
注意:匿名函数无法被解绑,必须改写成有具体名的函数。
function fn(){
alert('你好')
}
//绑定事件
btn.addEventListener('click',fn)
//解绑事件
btn.removeEventListener('click',fn)
鼠标经过事件的区别
- mouseover 和 mouseout 会有冒泡效果
- mouseenter 和 mouseleave 没有冒泡效果 (推荐)
两种注册事件的区别
传统on注册(L0)
(1)同一个对象,后面注册的事件会覆盖前面注册(同一个事件)。
(2)直接使用null覆盖偶就可以实现事件的解绑
(3)都是冒泡阶段执行的
事件监听注册(L2)
(1)语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
(2)后面注册的事件不会覆盖前面注册的事件(同一个事件)
(3)可以通过第三个参数去确定是在冒泡或者捕获阶段执行
(4)必须使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)
(5)匿名函数无法被解绑
事件委托
优点:减少注册次数,可以提高程序性能。
原理:事件委托其实是利用事件冒泡的特点。(给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事 件)
语法:父级元素.addEventListener('click', function(){}) 执行父级点击事件
事件对象.target. tagName 可以获得真正触发事件的元素
tab栏切换改造
<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')
const items = document.querySelectorAll('.tab-content .item')
//添加点击事件 事件对象e
ul.addEventListener('click', function (e) {
//只有点击a标签才能进行添加和删除类的操作
if (e.target.tagName === "A") {
// 排他思想
// 干掉别人 移除类active
document.querySelector('.tab-nav .active').classList.remove('active')
//当前元素添加active 当前元素是e.target
//this指向的是ul,不能用this
e.target.classList.add('active')
// 下面5个大盒子 e.target.dataset.id获取的是字符串 '0'+1为'01'下面会报错
const i = +(e.target.dataset.id)
document.querySelector('.tab-content .active').classList.remove('active')
//方法1
//没有i,则需要自定义属性
// document.querySelector(`.tab-content .item:nth-child(${i + 1})`).classList.add('active')
//方法2
//获取item得到的是一个数组
items[i].classList.add('active')
}
})
//以下是利用for循环完成的
// for (let i = 0; i < as.length; i++) {
// // console.log(as[i])
// // 要给 5个链接绑定鼠标经过事件
// as[i].addEventListener('mouseenter', function () {
// // console.log('鼠标经过')
// // 排他思想
// // 干掉别人 移除类active
// document.querySelector('.tab-nav .active').classList.remove('active')
// // 我添加类 active this 当前的那个 a
// this.classList.add('active')
// // 下面5个大盒子 一一对应 .item
// // 干掉别人
// document.querySelector('.tab-content .active').classList.remove('active')
// // 对应序号的那个 item 显示 添加 active 类
// document.querySelector(`.tab-content .item:nth-child(${i + 1})`).classList.add('active')
// })
// }
</script>
其他事件
页面加载事件
事件名:load
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件
监听页面所有资源加载完毕:给 window 添加 load 事件
语法:windows.addEventListener('load',function(){})
注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件。
事件名:DOMContentLoaded
当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像等完全加载。
语法:document.addEventListener('DOMContentLoaded',function(){})
监听页面DOM加载完毕:给 document 添加 DOMContentLoaded 事件
元素滚动事件
事件名:scroll
监听整个页面滚动:给 window 或 document 添加 scroll 事件
语法:windows.addEventListener('scroll',function(){})
- 监听某个元素的内部滚动直接给某个元素加即可
元素.scrollTo(x, y)
scrollTo() 方法可把内容滚动到指定的坐标
scrollLeft和scrollTop (属性)
(1)获取被卷去的大小
(2)获取元素内容往左、往上滚出去看不到的距离。
(3)这两个值是可读写的(可读可赋值)
//页面滚动事件
windows.addEventListener('scroll',function(){
//document.documentElement HTML 文档返回对象为HTML元素
const n = document.documentElement.scrollTop
console.log(n)
})
页面滚动显示隐藏侧边栏
<script>
//获取元素
const elevator = document.querySelector('.xtx-elevator ')
const entry = document.querySelector('.xtx_entry ')
//当前页面大于300px,就显示电梯
//给页面添加滚动事件
window.addEventListener('scroll', function () {
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
// console.log(entry.offsetTop);
})
//点击返回顶部
const backTop = document.querySelector('#backTop')
backTop.addEventListener('click', function () {
document.documentElement.scrollTop = 0
//window.scrollTo(0,0) 了解
})
</script>
页面尺寸事件
在窗口尺寸改变的时候触发事件:
windows.addEventListener(' resize ',function(){})
获取宽高
(1)获取元素的可见部分宽高(不包含边框,margin,滚动条等)
(2)clientWidth和clientHeight
元素尺寸与位置
获取宽高:
(1)offsetWidth和offsetHeight
(2)获取元素的自身宽高、包含元素自身设置的宽高、padding、border。
(3)获取出来的是数值,方便计算。
(4)注意: 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0。
获取位置:
(1)offsetLeft和offsetTop 注意是只读属性
(2)获取元素距离自己定位父级元素的左、上距离(受父级定位的影响)
offsetTop和offsetLeft 得到位置以谁为准?
(1)带有定位的父级
(2)如果都没有则以文档左上角为准
仿新浪固定导航栏案例
<div class="content"></div>
<div class="backtop">
<img src="./images/close2.png" alt="">
<a href="javascript:;"></a>
</div>
<script>
// 0 获取元素
let backtop = document.querySelector('.backtop')
// 页面滚动事件
window.addEventListener('scroll', function () {
// 2. 页面检测滚动的距离
// console.log(document.documentElement.scrollTop)
let num = document.documentElement.scrollTop
// 3. 进行判断显示和隐藏
if (num >= 500) {
//显示那个元素
backtop.style.display = 'block'
} else {
// 否则隐藏元素
backtop.style.display = 'none'
}
})
// 点击链接返回顶部 backtop.children[1]
backtop.children[1].addEventListener('click', function () {
// 返回顶部
// scrollTop 可读写
document.documentElement.scrollTop = 0
})
</script>
总结
综合案例
复选框
<script>
//获取大复选框
const allCheck = document.querySelector('#checkAll')
//获取小复选框
const cks = document.querySelectorAll('.ck')
//添加大复选框点击事件
allCheck.addEventListener('click', function () {
for (let i = 0; i < cks.length; i++) {
cks[i].checked = this.checked
}
})
//添加小复选框点击事件
for (let i = 0; i < cks.length; i++) {
cks[i].addEventListener('click', function () {
allCheck.checked = document.querySelectorAll('.ck:checked').length === cks.length
})
}
</script>
电梯导航
<div class="aside">
<div class="item active">男装/女装</div>
<div class="item">儿童服装/游乐园</div>
<div class="item">电子产品</div>
<div class="item">电影/美食</div>
</div>
<div class="content">
<div class="neirong content1">男装/女装</div>
<div class="neirong content2">儿童服装/游乐园</div>
<div class="neirong content3">电子产品</div>
<div class="neirong content4">电影/美食</div>
</div>
<script>
//获取元素
const items = document.querySelectorAll('.aside .item')
const neirongs = document.querySelectorAll('.content .neirong')
// 左侧aside 模块 点击谁,谁高亮
for (let i = 0; i < items.length; i++) {
items[i].addEventListener('click', function () {
// 找到上一个active 移除类
document.querySelector('.aside .active').classList.remove('active')
// 点击谁谁添加类
this.classList.add('active')
// 右侧内容跟随走动 让页面滚动到对应的offsetTop值位置
// console.log(neirongs[i].offsetTop) 找到数据值 不用给单位
document.documentElement.scrollTop = neirongs[i].offsetTop
})
}
</script>
<script>
//第一大模块
(function () {
//获取元素
const elevator = document.querySelector('.xtx-elevator ')
const entry = document.querySelector('.xtx_entry ')
//当前页面大于300px,就显示电梯
//给页面添加滚动事件
window.addEventListener('scroll', function () {
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
// console.log(entry.offsetTop);
})
//点击返回顶部
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) {
if (e.target.tagName === 'A' && e.target.dataset.name) {
//先获取这个类名
const old = document.querySelector('.xtx-elevator-list .active')
if (old) old.classList.remove('active')
//当前元素添加active
e.target.classList.add('active')
//获得自定义属性
//根据小盒子的属性值选择合适的大盒子
const top = document.querySelector(`.xtx_goods_${e.target.dataset.name}`).offsetTop
//获取对应的大盒子内容,让页面滚动到对应的位置
document.documentElement.scrollTop = top
}
})
//页面滚动,可以根据大盒子选小盒子,添加active
window.addEventListener('scroll', function () {
//先获取这个类名
const old = document.querySelector('.xtx-elevator-list .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>
购物车加减操作
<div>
<input type="text" id="total" value="0" readonly>
<input type="button" value="+" id="add">
<input type="button" value="-" id="reduce" disabled>
<script>
//获取元素
const total = document.querySelector('#total')
const add = document.querySelector('#add')
const reduce = document.querySelector('#reduce')
//加号操作
add.addEventListener('click', function () {
// console.log(typeof total.value) string
// i++ 隐式转换
total.value++
reduce.disabled = false
})
//减号操作
reduce.addEventListener('click', function () {
// console.log(typeof total.value) string
// i-- 隐式转换
total.value--
add.disabled = false
})
// 2 === '2'
</script>
</div>