事件流
事件捕获和事件冒泡
简言之,捕获阶段是【从父到子】的传导过程,冒泡阶段是【从子向父】的传导过程。实际工作都是使用事件冒泡为主
如果事件是在冒泡阶段执行的,我们称为冒泡模式,它会先执行子盒子事件再去执行父盒子事件,默认是冒泡模式。
如果事件是在捕获阶段执行的,我们称为捕获模式,它会先执行父盒子事件再去执行子盒子事件。
<body>
<h3>事件流</h3>
<p>事件流是事件在执行时的底层机制,主要体现在父子盒子之间事件的执行上。</p>
<div class="outer">
<div class="inner"></div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer')
const inner = document.querySelector('.inner')
// 外层的盒子
outer.addEventListener('click', function () {
console.log('outer...')
}, true) // true 表示在捕获阶段执行事件
// 中间的盒子
outer.addEventListener('click', function () {
console.log('inner...')
}, true)
</script>
</body>
addEventListener
第3个参数决定了事件是在捕获阶段触发还是在冒泡阶段触发addEventListener
第3个参数为true
表示捕获阶段触发,false
表示冒泡阶段触发,默认值为false
- 事件流只会在父子元素具有相同事件类型时才会产生影响
- 绝大部分场景都采用默认的冒泡模式(其中一个原因是早期 IE 不支持捕获)
阻止冒泡
阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。
<body>
<div class="outer">
<div class="inner">
<div class="child"></div>
</div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer')
const inner = document.querySelector('.inner')
const child = document.querySelector('.child')
// 外层的盒子
outer.addEventListener('click', function () {
console.log('outer...')
})
// 中间的盒子
inner.addEventListener('click', function (ev) {
console.log('inner...')
// 阻止事件冒泡
ev.stopPropagation()
})
// 内层的盒子
child.addEventListener('click', function (ev) {
console.log('child...')
// 借助事件对象,阻止事件向上冒泡
ev.stopPropagation()
})
</script>
</body>
e.stopPropagation()
可以阻止事件冒泡,捕获阶段也有效,e
为事件对象
mouseover
和mouseout
会有冒泡效果
mouseenter
和mouseleave
没有冒泡效果 (推荐)
阻止默认行为
某些情况下需要阻止默认行为的发生,比如 阻止 链接的跳转,表单域跳转
e.preventDefault()
可以阻止默认行为,e
为事件对象
解绑事件
on事件方式,直接使用null覆盖偶就可以实现事件的解绑
btn.onclick = function(){
alert('点击了')
}
// 解绑事件
btn.onclick = null
addEventListener
方式,必须使用:removeEventListener
(事件类型, 事件处理函数, [获取捕获或者冒泡阶段])
function fn(){
alert('点击了')
}
// 绑定事件
btn.addEventListener('click',fn)
// 解绑事件
btn.removeEventListener('click',fn)
匿名函数无法被解绑
事件委托
事件委托是利用事件流的特征解决一些现实开发需求的知识技巧,主要的作用是提升程序效率。 大量的事件监听是比较耗费性能的,有些情况下只需找到对应的父节点进行操作即可
给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
事件对象.target. tagName
可以获得真正触发事件的元素
<script>
// 假设页面中有 10000 个 button 元素
const buttons = document.querySelectorAll('table button')
// 假设上述的 10000 个 buttom 元素共同的祖先元素是 table
const parents = document.querySelector('table')
parents.addEventListener('click', function (ev) {
// console.log(ev.target);
// 只有 button 元素才会真正去执行逻辑
if(ev.target.tagName === 'BUTTON') {
// 执行的逻辑
}
})
</script>
其他事件
页面加载事件
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件
事件名:load
监听页面所有资源加载完毕:
window.addEventListener('load', function() {
// xxxxx
})
元素滚动事件
滚动条在滚动的时候持续触发的事件
很多网页需要检测用户把页面滚动到某个区域后做一些处理, 比如固定导航栏,比如返回顶部
window.addEventListener('load', function() {
// xxxxx
})
页面滚动事件-获取位置
scrollLeft
和scrollTop
(属性)
- 获取被卷去的大小
- 获取元素内容往左、往上滚出去看不到的距离
- 这两个值是可读写的
div.addEventListener('scroll',function(){
console.log(this.scrollTop)
})
开发中,我们经常检测页面滚动的距离,比如页面滚动100像素,就可以显示一个元素,或者固定一个元素
document.documentElement
HTML 文档返回对象为HTML元素
window.addEventListener('scroll',function(){
const n = document.documentElement.scrollTop
console.log(n)
})
-
被卷去的头部或者左侧用那个属性?是否可以读取和修改?
scrollTop
/scrollLeft
, 可以读取,也可以修改(赋值) -
检测页面滚动的头部距离(被卷去的头部)用那个属性?
document.documentElement.scrollTop
页面滚动事件-滚动到指定的坐标
scrollTo()
方法可把内容滚动到指定的坐标,元素.scrollTo(x, y)
window.scrollTo(0,1000)
// 页面滚到y轴1000像素的位置
页面尺寸事件
会在窗口尺寸改变的时候触发事件:resize
获取宽高:
- 获取元素的可见部分宽高(不包含边框,margin,滚动条等
clientWidth
和clientHeight
- 获取出来的是数值,方便计算
元素尺寸于位置
简单说,就是通过js的方式,得到元素在页面中的位置,获取元素的自身宽高、包含元素自身设置的宽高、padding、border
获取出来的是数值,方便计算,注意: 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0
offsetLeft
和offsetTop
注意是只读属性
-
offsetWidth
和offsetHeight
是得到元素什么的宽高?内容 + padding + border
-
offsetTop
和offsetLeft
得到位置以谁为准?带有定位的父级
如果都没有则以 文档左上角 为准
element.getBoundingClientRect()
方法返回元素的大小及其相对于视口的位置
属性 | 作用 | 说明 |
---|---|---|
scrollLeft 和scrollTop | 被卷去的头部和左侧 | 配合页面滚动来用,可读写 |
clientWidth 和 clientHeight | 获得元素宽度和高度 | 不包含border,margin,滚动条 用于js获取元素大小,只读属性 |
offsetWidth 和offsetHeight | 获得元素宽度和高度 | 包含border、padding,滚动条等,只读 |
offsetLeft 和offsetTop | 获取元素距离自己定位父级元素的左、上距离 | 获取元素位置的时候使用,只读属性 |
日期对象
掌握 Date 日期对象的使用,动态获取当前计算机的时间。
ECMAScript 中内置了获取系统时间的对象 Date,使用 Date 时与之前学习的内置对象 console 和 Math 不同,它需要借助 new 关键字才能使用。
// 1. 实例化
// const date = new Date(); // 系统默认时间
const date = new Date('2020-05-01') // 指定时间
// date 变量即所谓的时间对象
console.log(typeof date)
// 1. 实例化
const date = new Date();
// 2. 调用时间对象方法
// 通过方法分别获取年、月、日,时、分、秒
const year = date.getFullYear(); // 四位年份
const month = date.getMonth(); // 0 ~ 11
getFullYear
获取四位年份
etMonth
获取月份,取值为 0 ~ 11
getDate
获取月份中的每一天,不同月份取值也不相同
getDay
获取星期,取值为 0 ~ 6
getHours
获取小时,取值为 0 ~ 23
getMinutes
获取分钟,取值为 0 ~ 59
getSeconds
获取秒,取值为 0 ~ 59
时间戳是指1970年01月01日00时00分00秒起至现在的总秒数或毫秒数,它是一种特殊的计量时间的方式。
ECMAScript 中时间戳是以毫秒计的。
将来的时间戳 - 现在的时间戳 = 剩余时间毫秒数,剩余时间毫秒数 转换为 剩余时间的 年月日时分秒 就是 倒计时时间,比如 将来时间戳 2000ms - 现在时间戳 1000ms = 1000ms,1000ms 转换为就是 0小时0分1秒
**三种方式获取时间戳 **
// 1. 实例化
const date = new Date()
// 2. 获取时间戳
console.log(date.getTime())
// 还有一种获取时间戳的方法
console.log(+new Date())
// 还有一种获取时间戳的方法
console.log(Date.now())
Date.now()
只能得到当前的时间戳, 而前面两种可以返回指定时间的时间戳
节点操作
回顾之前 DOM 的操作都是针对元素节点的属性或文本的,除此之外也有专门针对元素节点本身的操作,如插入、复制、删除、替换等。
DOM树里每一个内容都称之为节点,元素节点(所有的标签 比如 body、 div,html 是根节点),属性节点(所有的属性 比如 href),文本节点(所有的文本)。
查找节点
DOM 树中的任意节点都不是孤立存在的,它们要么是父子关系,要么是兄弟关系,不仅如此,我们可以依据节点之间的关系查找节点。
父节点查找:
parentNode
属性- 返回最近一级的父节点 找不到返回为null
子元素.parentNode
子节点查找
childNodes
,获得所有子节点、包括文本节点(空格、换行)、注释节点等- children 属性 (重点),仅获得所有元素节点,返回的还是一个伪数组
父元素.children
兄弟关系查找:
- 下一个兄弟节点,
nextElementSibling
属性 - 上一个兄弟节点,
previousElementSibling
属性
插入节点
在已有的 DOM 节点中插入新的 DOM 节点时,需要关注两个关键因素:首先要得到新的 DOM 节点,其次在哪个位置插入这个节点
-
createElement
动态创建任意 DOM 节点 -
cloneNode
复制现有的 DOM 节点,传入参数 true 会复制所有子节点,若为false,则代表克隆时不包含后代节点,默认为false
-
appendChild
在末尾(结束标签前)插入节点 -
insertBefore
,父元素.insertBefore
(要插入的元素,在那个元素前面)
<body>
<h3>插入节点</h3>
<p>在现有 dom 结构基础上插入新的元素节点</p>
<hr>
<!-- 普通盒子 -->
<div class="box"></div>
<!-- 点击按钮向 box 盒子插入节点 -->
<button class="btn">插入节点</button>
<script>
// 点击按钮,在网页中插入节点
const btn = document.querySelector('.btn')
btn.addEventListener('click', function () {
// 1. 获得一个 DOM 元素节点
const p = document.createElement('p')
p.innerText = '创建的新的p标签'
p.className = 'info'
// 复制原有的 DOM 节点
const p2 = document.querySelector('p').cloneNode(true)
p2.style.color = 'red'
// 2. 插入盒子 box 盒子
document.querySelector('.box').appendChild(p)
document.querySelector('.box').appendChild(p2)
})
</script>
</body>
删除节点
在 JavaScript 原生DOM操作中,要删除元素必须通过父元素删除 父元素.removeChild(要删除的元素)
如不存在父子关系则删除不成功
删除节点和隐藏节点(
display:none
) 有区别的: 隐藏节点还是存在的,但是删除,则从html
中删除节点
M端事件
移动端也有自己独特的地方。比如触屏事件 touch(也称触摸事件),Android 和 IOS 都有。触屏事件 touch(也称触摸事件),Android 和 IOS 都有。touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作
触屏touch事件 | 说明 |
---|---|
touchstart | 手指触摸到一个DOM元素时触发 |
touchmove | 手指在一个DOM元素上滑动时触发 |
touchend | 手指在一个DOM元素上移开时触发 |
swiper
-
插件: 就是别人写好的一些代码,我们只需要复制对应的代码,就可以直接实现对应的效果
-
熟悉官网,了解这个插件可以完成什么需求 https://www.swiper.com.cn/
-
看在线演示,找到符合自己需求的demo https://www.swiper.com.cn/demo/index.html
-
查看基本使用流程 https://www.swiper.com.cn/usage/index.html
-
查看APi文档,去配置自己的插件 https://www.swiper.com.cn/api/index.html
-
注意: 多个swiper同时使用的时候, 类名需要注意区分
重绘和回流
-
浏览器是如何进行界面渲染的
解析(Parser)HTML,生成DOM树(DOM Tree)
同时解析(Parser) CSS,生成样式规则 (Style Rules)
根据DOM树和样式规则,生成渲染树(Render Tree)
进行布局 Layout(回流/重排):根据生成的渲染树,得到节点的几何信息(位置,大小)
进行绘制 Painting(重绘): 根据计算和获取的信息进行整个页面的绘制
Display: 展示在页面上
-
重绘和回流
回流(重排)
当 Render Tree 中部分或者全部元素的尺寸、结构、布局等发生改变时,浏览器就会重新渲染部分或全部文档的
过程称为 回流。
重绘
由于节点(元素)的样式的改变并不影响它在文档流中的位置和文档布局时(比如:color、background-color、
outline等), 称为重绘
重绘不一定引起回流,而回流一定会引起重绘。
- 会导致回流(重排)的操作:
- 页面的首次刷新
- 浏览器的窗口大小发生改变
- 元素的大小或位置发生改变
- 改变字体的大小
- 内容的变化(如:input框的输入,图片的大小)
- 激活css伪类 (如::hover)
- 脚本操作DOM(添加或者删除可见的DOM元素)