事件流
事件流是指事件完整执行过程中的流动路径
一个事件流需要经过两个阶段:捕获阶段,冒泡阶段
捕获阶段是在dom树里获取目标元素的过程,从大到小
冒泡阶段是获取以后回到开始,从小到大,像冒泡一样
实际开发中大部分只用到了冒泡阶段
事件捕获
从Dom的根元素开始去执行对应的事件(从外到里)
javascript">dom.addEventListener('事件类型',function(){},true)
true是捕获,默认是false
false是冒泡阶段,如果你只使用 L0
事件监听,那么事件监听器只会在冒泡阶段触发,不会在捕获阶段触发,因为ie等浏览器不支持捕获,或者说没有冒泡和捕获这个概念
1、事件覆盖情况:
L0(传统on<事件类型>注册):对于同一个对象,如果后面注册了相同类型的事件,它会覆盖前面注册的事件。例如,如果一个元素上先后注册了两个onclick事件,那么只有后面注册的那个事件会被触发。
L2(事件监听注册addEventListener):使用这种方法注册事件时,后面注册的同类型事件不会覆盖前面注册的事件。这意味着,可以在同一个元素上注册多个相同类型的事件,并且它们都会被触发。
2、事件解绑方式:L0:直接使用null赋值就可以实现事件的解绑,例如对象.onclick = null。
L2:解绑事件需要使用removeEventListener方法,并传入相应的事件类型、事件处理函数以及捕获或冒泡阶段(如果绑定时指定了的话)。需要注意的是,如果使用匿名函数作为事件处理函数,那么将无法解绑该事件,因为无法再次引用到相同的匿名函数。
3、执行阶段:L0:注册的事件都是在冒泡阶段执行的。
L2:注册事件时可以通过第三个参数来指定事件是在捕获阶段还是冒泡阶段执行。如果不指定第三个参数或传入false,则事件默认在冒泡阶段执行;如果传入true,则事件在捕获阶段执行。
事件冒泡
事件冒泡:当一个元素的事件被触发时,同意的事件将会在该元素的所有祖先元素依次触发
javascript">dom.addEventListener('事件类型',function(){})
L2事件监听的第三个参数是false
阻止冒泡
冒泡会把事件传递到父元素,有时候我们需要将事件的影响限制到当前元素里,就需要阻止事件冒泡
阻止事件冒泡首先要拿到事件对象:
这个方法不光可以阻断冒泡,可以阻断其他时间流动传播,就是说也可以阻断捕获阶段的事件传播
在哪里截断就在哪里加这句方法,记住此方法要调用事件对象e
阻止默认行为
有些网页元素有默认行为,比如右键弹出菜单栏这种,如果想阻止默认事件,就需要加上
解绑事件
之前我们只学了监听事件,有时候监听结束后不需要监听了,就需要解绑事件
在L0旧语法里是这么解绑的
L2里使用addEventListener()添加监听,就要使用removeEventListener()来解绑
javascript">removeEventListener(事件类型,事件处理函数,[获取捕获或冒泡阶段])
因为要添加事件处理函数作为参数,匿名函数没有名字,所以不能被解绑
加了中括号的参数表示可写可不写
fn不能加小括号,函数回调不用加小括号
鼠标经过事件的区别
在只有一级,没有父子级的时候,二者没什么区别
mouseover和mouseout在父子级都存在的时候会有冒泡效果,自己的效果会冒泡到父级上
mouseenter和mouseleae则没有这个问题,推荐使用这种用法
事件委托
事件相对于快递,给很多元素绑定事件的时候,在以前我们是才能for循环的方式,给每个元素绑定事件,就相当于每个快递1对1的送给每个人;但是有了驿站,就可以把快递托给驿站,就不需要一个一个的注册事件,这叫事件委托,此时的驿站和个人是一种父子级的关系
事件委托是一种事件注册的小技巧
原理:当我们想要给很多个子元素注册一样的事件的时候,例如点击小圆点切换图片(轮播图),就需要单独给每个小圆点添加监听;但是给父元素添加监听的时候,我们触发子元素的时候,会冒泡到父元素,然后触发父元素绑定的事件。
所以事件委托的原理就是事件冒泡
这样可以提高性能,减少注册次数
写一个看看:
父元素里的子元素在监听到事件以后会执行父元素绑定的函数:
javascript"> const box = document.querySelector('div')
box.addEventListener('click', function (e) {
//alert('点上了')
console.log(e.target);
})
点击不同的span,e.target的属性值也不一样
根据事件对象的特性改变span的背景颜色:
javascript"> <script>
const box = document.querySelector('div')
box.addEventListener('click', function (e) {
//alert('点上了')
console.log(e.target);
e.target.style.backgroundColor='purple'
})
</script>
但是这样有一个问题:父级绑定了以后所有的子级触发事件后都会执行父元素绑定的函数,有时候并不是所有的子级都需要触发,类名不同或者标签不同的元素不需要这个事件,怎么做到分离父级和 一部分子级?
事实证明人类对事件对象的开发程度不足1%:
展开这个箭头:
可以发现这些目标是有nodeName的,我们可以靠nodeName来区分,只对span添加委托:
javascript"> const box = document.querySelector('div')
box.addEventListener('click', function (e) {
//alert('点上了')
//console.log(e);
if(e.target.nodeName==='SPAN')
e.target.style.backgroundColor='purple'
})
使用事件委托来重做一下之前的小圆点切换图片功能:
一个需要注意的点:之前我们是用i来访问选择的li,现在没有i了,怎么知道选中的是哪个li?
使用自定义属性
定义:
javascript"><div data-id="0"></div>
获取:
javascript"> const div=document.querySelector('div') console.log(div.dataset.id)
像这样,来访问li
但是!孩子们,注意一个问题,此时你获得的data-id是字符串,不能之间当数字使,取过来会当字符串使用
js:
javascript"> //事件委托写法
const ul = document.querySelector('ul')
ul.addEventListener('click', function (e) {
//console.log(e.target.dataset.id);
// 隐式转换,转换为数字型
const id =+e.target.dataset.id
toggle(id)
clearInterval(timeId)
i = id
timeId = setInterval(function () {
next.click()
}, 1000)
})
html:
<ul class="slider-indicator">
<li class="active" data-id="0"></li>
<li data-id="1"></li>
<li data-id="2"></li>
<li data-id="3"></li>
</ul>
其它事件
除了之前学的鼠标事件、键盘事件、焦点事件、文本事件等,还有其他事件
页面加载事件
在打开一个网页的时候,会有许多外部资源被加载,比如图片、外联CSS、JavaScript等,我们需要等这些元素加载完在打开网页;或者在body前面写的js没办法加载dom树,所以我们需要等待(load)
事件名:load
javascript">window.addEventListener('load',function(){
})
load事件的意思是等加载完毕,再去执行回调函数
但是现在没什么人这么写,因为如果要等待dom树加载完为什么要写body前面呢?直接写在body后面就不用操心这个问题了
有时候网络出了问题或者网页加载比较慢的时候,网页里会先加载出来html元素,css和js等还没有渲染出来。这种只加载出html骨架就触发的事件叫DOMContentLoaded
相比load事件,DOMContentLoaded事件触发会比load快,因为不用等样式表、图片等外部资源加载好,用户体验更好
javascript">document.addEventListener('DOMContentLoaded',function(){
})
元素滚动事件
页面内有些元素是根据滚动在不同地方的时候出现的,这个关于网页滚动的事件就叫scroll(滚动事件)
首先要确定的是谁在滚动?window
javascript"> window.addEventListener('scroll', function () {
console.log('页面滚动了');
})
每滚动1像素就会检测到:
介绍页面的新属性
srcollLeft:左侧被卷去的距离
srcollTop:顶部被卷去的距离
出现的数字有两个特点:不带单位、可读写(简称可访问可赋值)
可以通过页面滚动的距离来设置元素的显示:
javascript"> const elevator = document.querySelector('.xianshi')
window.addEventListener('scroll', function () {
//console.log('页面滚动了');
//console.log(document.documentElement.scrollTop)
let top = document.documentElement.scrollTop
if (top >= 300) {
elevator.style.opacity = 1
}
})
说明数据可读可写
页面尺寸事件
随着页面的放大和缩小触发事件
通过clientWidth和clientHeight来获取盒子的宽度:
但是clientWidth和clientHeight是不包含padding和border的
而offsetWidth和offsetHeight则可以包含padding和border,可以获取宽高
获取尺寸的数据为数值,可以方便计算
前提是盒子为可视化盒子,如果盒子被隐藏起来了,获取的数值为0
offsetLeft和offsetTop可以获取元素距离定位父级元素(如果最近一级的父级元素不带定位,就往上找一级,直到找到带定位的父级元素,测量和该元素的距离为offsetLeft的值)的左、上距离,是只读属性做不了计算
demo
解决问题:初次没有active类的报错问题
解决办法:先获取类,分情况讨论:有active这个类/没有这个类
如果当前对象没有这个类,就为他添加此类/如果有这个类就去除
html{
scroll-behavior: smooth;
/* 滑动流畅 */
}
在点击事件之后,电梯字体的颜色也要改变,来告诉用户当前处于哪个模块
所以只要一滚动,就要移除当前的active,在滚动之后再加上
本来我觉得单独获取与for循环获取还是后者更方便,但是品客老师说这样不方便后期的修改,降低的代码的可用性
javascript">//电梯函数模块
(function () {
const elevator = document.querySelector('.xtx-elevator')
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 >= 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) {
if (e.target.tagName === 'A' && e.target.dataset.name) {
const old = document.querySelector('.xtx-elevator-list .active')
// if (old) old.classList.remove('active')
// e.target.classList.add('active')
//console.log(document.querySelector(`.xtx_goods_${e.target.dataset.name}`).offsetTop)
const top = document.querySelector(`.xtx_goods_${e.target.dataset.name}`).offsetTop
document.documentElement.scrollTop = top
}
})
window.addEventListener('scroll', function () {
const old = document.querySelector('.xtx-elevator-list .active')
if (old) old.classList.remove('active')
// 获取所有 <li> 元素
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')
}
})
})();
就在此时此刻,我的外卖被偷了
我永远痛恨偷外卖的4全家