事件流和事件委托及其他事件

事件流指的是事件完整执行过程中的流动路径,简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父。

事件流流程图

事件捕获:

从DOM的根元素开始去执行对应的事件(从外到里)

DOM.addEventListener(事件类型,事件处理函数,是否使用捕获机制)

addEventListener第三个参数传入 true 代表是捕获阶段触发(很少使用)

若是用LO事件监听,则只有冒泡阶段,没有捕获

事件流冒泡

当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡。

简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件。

阻止冒泡

能够写出阻止冒泡的代码:事件对象.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)

鼠标经过事件的区别

mouseovermouseout 会有冒泡效果

mouseentermouselegve 沒有冒泡效果(推荐)

两种注册事件的区别

传统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事件里面获取被卷去的距离

scrollLeft和scrollTop
const div = document.querySelector('div')
div.addEventListener('scroll', function () {
  // 被卷去的头部
  console.log(div.scrollTop)
  // 被卷去的左距
  console.log(this.scrollLeft)
})

开发中,我们经常检测页面滚动的距离,比如页面滚动100像素,就可以显示一个元素,或者固定一个元素

Demo

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)// 移动端倍数

元素尺寸与位置

前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个元素,就可以做某些事。

属性作用说明
scrollLeft scrollTop被卷去的头部和左侧配合页面滚动来用,可读写
clientwidth clientHeight获得元素宽度和高度不包含border,margin滚动条 用于js获取元素大小,只读属性
offsetWidth offsetHeight获得元素宽度和高度包含border,padding,滚动条等,只读
offsetLeft offsetTop获取元素距离自己定位父
级元素的左、上距离
获取元素位置的时候使用,只读属性

下面通过js的方式,得到元素在页面中的位置,页面滚动到这个位置,就可以做某些操作,省去计算了。

获取宽高:

获取元素的自身宽高、包含元素自身设置的宽高、padding、border

获取出来的是数值,方便计算

注意:获取的是可视宽高,如果盒子是隐藏的,获取的结果是0

获取位置:Demo

获取元素距离自己定位父级元素的左、上距离

offsetLeftoffsetTop 注意是只读属性

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,页面跳转到对应大盒子位置

核心思想:

把对应大盒子的offfsetTopdocument.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')
    }
  })
})();

原创文章,作者:霍欣标,如若转载,请注明出处:https://www.bigengwu.cn/xue/117.html

霍欣标的头像霍欣标
上一篇 2024-08-04
下一篇 2024-08-21

相关推荐

博主人懒,应管局要求暂不开启站内私信和评论功能,如需帮助请发邮件。

邮箱账号:1969600480@qq.com