2022.10.18
一.案例 - 分页渲染
分析:
1. 打开页面
+ 根据 list 数组内的部分数据( 根据 第几页 和 一页显示多少条 )渲染 ul 内的 li 标签名
+ 根据数据的 总数 和 一页显示多少条 计算出一共多少页, 渲染指定标签内的文本
+ 根据 当前是第几页 决定 上一页 按钮是否具有 disable 类名
+ 根据 当前是第几页 决定 下一页 按钮是否具有 disable 类名
2. 点击下一页按钮
+ 当前页改变(+1)
+ 根据 list 数组内的部分数据( 根据 第几页 和 一页显示多少条 )渲染 ul 内的 li 标签名
+ 根据数据的 总数 和 一页显示多少条 计算出一共多少页, 渲染指定标签内的文本
+ 根据 当前是第几页 决定 上一页 按钮是否具有 disable 类名
+ 根据 当前是第几页 决定 下一页 按钮是否具有 disable 类名
3. 点击上一页按钮
+ 当前页改变(-1)
+ 根据 list 数组内的部分数据( 根据 第几页 和 一页显示多少条 )渲染 ul 内的 li 标签名
+ 根据数据的 总数 和 一页显示多少条 计算出一共多少页, 渲染指定标签内的文本
+ 根据 当前是第几页 决定 上一页 按钮是否具有 disable 类名
+ 根据 当前是第几页 决定 下一页 按钮是否具有 disable 类名
4. 切换 select 标签的内容的时候
+ 当前页改变(=1)
+ 改变一页显示多少条
+ 根据 list 数组内的部分数据( 根据 第几页 和 一页显示多少条 )渲染 ul 内的 li 标签名
+ 根据数据的 总数 和 一页显示多少条 计算出一共多少页, 渲染指定标签内的文本
+ 根据 当前是第几页 决定 上一页 按钮是否具有 disable 类名
+ 根据 当前是第几页 决定 下一页 按钮是否具有 disable 类名
分析:
1. 书写一个函数(bindHtml)
+ 根据 list 数组内的部分数据( 根据 第几页 和 一页显示多少条 )渲染 ul 内的 li 标签名
+ 根据数据的 总数 和 一页显示多少条 计算出一共多少页, 渲染指定标签内的文本
+ 根据 当前是第几页 决定 上一页 按钮是否具有 disable 类名
+ 根据 当前是第几页 决定 下一页 按钮是否具有 disable 类名
2. 打开页面
+ 执行一遍 bindHtml
3. 点击下一页
+ 改变当前页(+1)
+ 执行一遍 bindHtml
4. 点击上一页
+ 改变当前页(-1)
+ 执行一遍 bindHtml
5. 切换 select 标签的内容的时候
+ 改变一页显示多少条
+ 改变当前页(=1)
+ 执行一遍 bindHtml
// 一页显示 x个 可选 通过 叠加覆盖 进行实现
<div class="header">网页头部</div>
<div class="pagination">
<span class="prev"><</span>
<span class="total">1 / 100</span>
<span class="next">></span>
<select>
<option value="4">4</option>
<option value="8">8</option>
<option value="12">12</option>
<option value="16">16</option>
</select>
</div>
<ul class="content">
<li>
<img src="" alt="">
<p>名称</p>
<p>城市: xx</p>
<p>地址: xx</p>
<p>价格: xx</p>
<p>时间: xx</p>
</li>
</ul>
<div class="footer">网页底部</div>
<script src="dm_list.js"></script> //此时的 外连接 只是一个 存入数据 的数组 模拟服务器拿数据
<script>
// 0. 获取元素
// 0-1. 获取承载页面的大盒子
var contentBox = document.querySelector('ul')
// 0-2. 获取到总数文本盒子
var totalBox = document.querySelector('.total')
// 0-3. 获取到上一页按钮
var prevBtn = document.querySelector('.prev')
// 0-4. 获取到下一页按钮
var nextBtn = document.querySelector('.next')
// 0-5. 获取到 select 标签
var selectBox = document.querySelector('select')
// 0. 准备变量
// 0-1. 准备一个变量表示当前是第几页
var current = 1
// 0-2. 准备一个变量表示一页多少条
var pagesize = 4
// 0-3. 准备一个变量表示一共多少页
var total = 0
// 1. 准备一个渲染页面的函数
function bindHtml() {
// 1-1. 根据 list 数组内的部分数据( 根据 第几页 和 一页显示多少条 )渲染 ul 内的 li 标签名
// 1-1-1. 从 list 内获取出部分数据(详见解释1)
var bindList = list.slice((current - 1) * pagesize, current * pagesize)
// 1-1-2. 根据获取出来的部分数据渲染页面
contentBox.innerHTML = bindList.reduce(function (prev, item) {
return prev + `
<li>
<img src="${ item.pic }" alt="">
<p>${ item.name }</p>
<p>城市: ${ item.city }</p>
<p>地址: ${ item.address }</p>
<p>价格: ${ item.price }</p>
<p>时间: ${ item.showTime }</p>
</li>
`
}, '')
// 1-2. 根据数据的 总数 和 一页显示多少条 计算出一共多少页, 渲染指定标签内的文本
// 1-2-1. 计算出总页数(详见解释2)
total = Math.ceil(list.length / pagesize)
// 1-2-2. 利用计算出的总页数来渲染 total 标签的文本内容
totalBox.innerText = `${ current } / ${ total }`
// 1-3. 根据 当前是第几页 决定 上一页 按钮是否具有 disable 类名(详见解释3)
prevBtn.className = current === 1 ? 'prev disable' : 'prev'
// 1-4. 根据 当前是第几页 决定 下一页 按钮是否具有 disable 类名
nextBtn.className = current === total ? 'next disable' : 'next'
}
// 2. 打开页面执行一遍渲染页面的函数
bindHtml()
// 3. 点击下一页按钮
// 3-1. 给下一页按钮添加点击事件
nextBtn.onclick = function () {
// 判断 如果当前页就是最后一页, 什么也不需要做了
if (current >= total) return
// 3-2. 改变当前页
current++
// 3-2. 在执行一遍 bindHtml
bindHtml()
}
// 4. 点击上一页按钮
// 4-1. 给上一页按钮添加点击事件
prevBtn.onclick = function () {
// 判断, 当前页就是第一页, 什么也不需要做了
if (current <= 1) return
// 4-2. 改变当前页(-1)
current--
// 4-3. 在执行一遍 bindHtml
bindHtml()
}
// 5. 切换 select 标签内容
// 注意: 事件是 onchange 事件
selectBox.onchange = function () {
// 5-1. 拿到 select 标签改变成什么了, 改变一页显示多少条
// 说明: select 标签的 value 值是什么 ? 你选中的那个 option 的 value(是一个字符串)
pagesize = selectBox.value - 0
// 5-2. 改变当前页
current = 1
// 5-3. 在执行一遍 bindHtml
bindHtml()
}
</script>
二.获取元素尺寸
-
指获取元素在文档流内的占地面积
offset 一套
+ 语法:
=> 元素.offsetWidth
=> 元素.offsetHeight- 得到: 该元素 内容 + padding + border 区域的尺寸
- 得到: 该元素 内容 + padding + border 区域的尺寸
client 一套
+ 语法:
=> 元素.clientWidth
=> 元素.clientHeight
+ 得到: 该元素 内容 + padding 区域的尺寸
三.获取元素偏移量
offsetParant
+ offset 一套偏移量的参考父级
+ 语法: 元素.offsetParent
+ 得到: 该元素的偏移量参考父元素
=> 该元素的定位父级
offset 一套
+ 语法:
=> 元素.offsetLeft
=> 元素.offsetTop
+ 得到: 该元素相对于 offsetParent 的偏移量
client 一套
+ 语法:
=> 元素.clientLeft
=> 元素.clientTop
+ 得到: 该元素(内容+padding区域) 和 border 左上角的距离
=> 也就是该元素 左边框 和 上边框 的宽度
四.获取可视窗口尺寸
BOM 级别的获取
+ 语法:
=> window.innerWidth
=> window.innerHeight
+ 特点: 获取到的是包含滚动条在内的尺寸
DOM 级别的获取
+ 语法:
=> document.documentElement.clientWidth
=> document.documentElement.clientHeight
+ 特点: 获取到的是不包含滚动条在内的尺寸
五.案例 - 瀑布流渲染页面
分析:
+ 打开页面
=> 展示部分数据
+ 达到合适的时机
=> 拿到下一页的部分数据
=> 叠加在已有数据的下面
说明:
+ 在做分页的时候, 我们是在展示数组的部分数据
+ 因为完整数组是在本地, 只需要对数组的内的数据进行提取就可以了
+ 在实现效果的时候, 使用一个 延时定时器 来模拟网络环境
+ 设置的延时时间设定在 2000 ~ 5000 ms 之间
//一页 8个 当 滚动条 向下滑动到达零界点 进行 loading 之后 出新新的内容
<div class="header">网页头部</div>
<ul class="content">
<!-- JS 渲染 -->
</ul>
<div class="loading">
<img src="./003.gif" alt="">
</div>
<div class="footer">网页底部</div>
<script src="./dm_list.js"></script>
<script>
// 0. 获取元素
// 0-1. 获取承载列表的 ul 盒子
var contentBox = document.querySelector('ul')
// 0-2. 获取 loading 盒子
var loadingBox = document.querySelector('.loading')
// 0. 准备一些变量
// 0-1. 准备一个变量表示当前页
var current = 1
// 0-2. 准备一个变量表示一页多少条
var pagesize = 8
// 0-3. 准备一个变量表示总页数
var total = Math.ceil(list.length / pagesize)
// 0-4. 准备一个变量当做开关
var flag = true
// 1. 书写一个渲染页面的函数
function bindHtml() {
// 1-1. 根据 当前第几页 和 一页多少条 截取数组内的部分数据
var bindList = list.slice((current - 1) * pagesize, current * pagesize)
// 1-2. 根据截取出来的部分数据渲染页面
// 使用的是 赋值符号(=) 设置 ul 内的文本内容
// 当你将来渲染第二页的时候, 会把第一页的内容覆盖
// 注意: 需要使用 += 符号来设置文本内容
// 注意: 如果你使用的是 +=, 那么 ul 内本身就不能有内容
contentBox.innerHTML += bindList.reduce(function (prev, item) {
return prev + `
<li>
<img src="${ item.pic }" alt="">
<p>${ item.name }</p>
<p>城市: ${ item.city }</p>
<p>地址: ${ item.address }</p>
<p>价格: ${ item.price }</p>
<p>时间: ${ item.showTime }</p>
</li>
`
}, '')
}
// 2. 打开页面执行一遍
bindHtml()
// 3. 在一个合适的时机
// 3-1. 需要一个滚动事件
window.onscroll = function () {
// 3-2. 一定不会加载下一页的情况
// 只要没有下一页了, 绝对不会再次加载写一页了
if (current >= total) return
// 3-2. 找到一个合适的时机
// 卷去的高度 + 可视窗口的高度 < ul上方偏移量 + ul的高度 表示未到达时机, 不需要加载下一页
var scroll_top = document.documentElement.scrollTop || document.body.scrollTop
var window_height = document.documentElement.clientHeight
var ul_top = contentBox.offsetTop
var ul_height = contentBox.offsetHeight
if (scroll_top + window_height < ul_top + ul_height) return
// 3-3. 判断如果开关是关闭的, 就不在加载下一页了
if (flag === false) return
// 3-4. 代码来到这里, 应该加载下一页了
// 把开关关闭
flag = false
// 首先让 loading 标签出现
loadingBox.style.display = 'flex'
// 为了观察效果, 使用一个延时定时器模拟网络加载效果
setTimeout(function () {
// 加载下一页
current++
bindHtml()
// 让 loading 效果消失
loadingBox.style.display = 'none'
// 代码执行到这里, 说明第二页的数据加载完毕了
// loading 效果消失了, 所有的一切都准备好了, 可以加载下一页了
flag = true
}, Math.floor(Math.random() * 3000) + 2000)
}
</script>
2022.10.19
一.认识DOM节点
1.认识DOM节点
-
节点(node): 组成页面的每一个环节(部分, 内容, 零件)
+ 页面中常见的节点
=> 元素节点: 页面上的标签
-> html: 最大的元素节点, 也叫作 根元素节点
=> 属性节点: 书写在标签身上的属性, 不作为独立节点出现
-> 只是用来描述标签的内容
-> 和任何节点不产生父子关系
=> 文本节点: 一切文本内容(包含换行和空格)
=> 注释节点: 一切注释内容(包含换行和空格)
2.获取节点的方式
-
获取元素节点
-
获取非常规元素节点
-> html: document.documentElement
-> head: document.head
-> body: document.body -
获取常规元素节点
-> getElementById()
-> getElementByClassName()
-> getElementByTagName()
-> querySelector()
-> querySelectorAll() -
获取节点
-
childNodes
语法: 节点.childNodes
得到: 该节点的所有子节点 -
children
语法: 节点.children
得到: 该节点的所有子元素节点 -
firstChild
语法: 节点.firstChild
得到: 该节点下的第一个子节点 -
firstElementChild
语法: 节点.firstElementChild
得到: 该节点下的第一个子元素节点 -
lastChild
语法: 节点.lastChild
得到: 该节点下的最后一个子节点 -
lastElementChild
语法: 节点.lastElementChild
得到: 该节点下的最后一个子元素节点 -
previousSibling
语法: 节点.previousSibling
得到: 该节点的上一个兄弟节点 -
previousElementSibling
语法: 节点.previousElementSibling
得到: 该节点的上一个兄弟元素节点 -
nextSibling
语法: 节点.nextSibling
得到: 该节点的下一个兄弟节点 -
nextElementSibling
语法: 节点.nextElementSibling
得到: 该节点的下一个兄弟元素节点 -
parentNode
语法: 节点.parentNode
得到: 该节点的父节点(大部分都是元素节点) -
parentElement
语法: 节点.parentElement
得到: 该节点的父元素节点 -
attributes
语法: 节点.attributes
得到: 该节点的所有属性节点
3.创建节点
- 使用 js 的方式创建出一个 节点来
-
创建元素节点
- 语法: document.createElement('标签名')
- 返回值: 一个被创建出来的元素节点
- 注意: 标签名可以是自定义标签名
-
创建文本节点
- 语法: document.createTextNode('文本内容')
- 返回值: 一个创建好的文本节点
4.插入节点
- 把一个节点插入到另一个节点内作为子节点存在
-
appendChild()
- 语法: 父节点.appendChild(子节点)
- 作用: 把该 子节点 插入到 父节点 内部, 排列在最后一个的位置
-
insertBefore()
- 语法: 父节点.insertBefore(要插入的子节点, 谁的前面)
- 作用: 把该 子节点 插入到 父节点 内部, 排列在某一个已经存在的节点前面
5.删除节点
- 指把某一个节点删除
-
removeChild()
- 语法: 父节点.removeChild(子节点)
- 作用: 把该 子节点 从 父节点 内移除
-
remove()
- 语法: 节点.remove()
- 作用: 把该节点删除
6.替换节点
- replaceChild()
- 语法: 父节点.replaceChild(换上节点, 换下节点)
- 作用: 在 父节点 内, 使用 换上节点 替换掉 换下节点
7.克隆节点
- 指: 把已知节点复制一份一模一样的
cloneNode
+ 语法: 节点.cloneNode(参数)
+ 参数:
=> 默认是 false, 表示不克隆后代节点
=> 选填是 true, 表示克隆后代节点
二.认识事件
- 注意: 所有 JS 的原生事件内没有大写字母
+ 我们对常见的事件进行了一些分类
=> 鼠标事件
=> 键盘事件
=> 浏览器事件
=> 表单事件
=> 触摸事件
=> 拖拽事件
=> 其他事件
1.鼠标事件
-
click 鼠标左键单击
ele.onclick = function () { console.log('鼠标左键单击') } -
dblclick 鼠标左键双击
ele.ondblclick = function () { console.log('鼠标左键双击') } -
contextmenu 鼠标右键单击
ele.oncontextmenu = function () { console.log('鼠标右键单击') } -
mousedown 鼠标按键按下
ele.onmousedown = function () { console.log('鼠标按键按下') } -
mouseup 鼠标按键抬起
ele.onmouseup = function () { console.log('鼠标按键抬起') } -
mousemove 鼠标移动事件
随着移动实时触发, 大概每秒 60 次捕获
ele.onmousemove = function () { console.log('鼠标移动') } -
mouseover 鼠标移入事件
ele.onmouseover = function () { console.log('鼠标移入') } -
mouseout 鼠标移出事件
ele.onmouseout = function () { console.log('鼠标移出') } -
mouseenter 鼠标移入(键入)事件
ele.onmouseenter = function () { console.log('鼠标移入了') } -
mouseleave 鼠标移出(离开)事件
ele.onmouseleave = function () { console.log('鼠标离开了') }
-
注意:
=> mouseover 和 mouseout 一套事件, 在移入移出子元素的时候也会触发
=> mouseenter 和 mouseleave 一套事件, 在移入移出子元素的时候不会触发
2.键盘事件
-
keydown 键盘按下事件 inp.onkeydown = function () { console.log('键盘按下了') }
-
keyup 键盘抬起事件 inp.onkeyup = function () { console.log('键盘抬起了') }
-
keypress 键盘键入事件 注意: 按下的按键可以真正键入内容 注意: 键入的内容需要和按下的内容一致 注意: 回车键也可以触发 inp.onkeypress = function () { console.log('键盘键入内容') }
键盘事件
+ 注意: 键盘事件一般不用于捕获输入内容
+ 注意: 键盘事件可以给所有元素绑定, 但不是所有元素都能触发
=> 键盘事件一般绑定给 window / document / 可聚焦元素
3.表单事件
-
focus 聚焦
inp.onfocus = function () { console.log('聚焦') } -
blur 失焦
inp.onblur = function () { console.log('失焦') } -
input 输入
只要表单内的内容改变, 随着改变实时触发 inp.oninput = function () { console.log('正在输入内容') } -
change 改变
表单的真实内容发生变化的时候才会触发 当你失焦以后, 如果和聚焦时的内容不一样, 才叫做真实内容改变 inp.onchange = function () { console.log('表单改变了') } -
reset 重置
注意: 表单的重置行为是 form 标签才有
注意: 该事件需要绑定给 form 标签, 通过点击 reset 按钮来触发
formEle.onreset = function () { console.log('表单重置了') } -
submit 提交
注意: 表单的提交行为是 form 标签才有
注意: 该事件需要绑定给 form 标签, 通过点击 submit 按钮来触发
formEle.onsubmit = function () { console.log('表单提交了') }
表单事件
+ 依赖表单行为触发的事件
+ 注意:
=> input 事件常用在 input / textarea 等元素上
=> change 事件常用在 checkbox / radio / select 等元素上
4.触摸事件
触摸事件
+ 指在移动端等可触摸屏幕设置上使用的事件
-
touchstart 触摸开始(手指接触到屏幕的瞬间)
ele.ontouchstart = function () { console.log('触摸开始') } -
touchmove 触摸移动(手指在屏幕上移动的时候)
ele.ontouchmove = function () { console.log('触摸移动') } -
touchend 触摸接触(手指离开屏幕的瞬间)
ele.ontouchend = function () { console.log('触摸结束') }
5.拖拽事件
拖拽事件
-
注意: 一般元素是不允许被拖拽的, 如果你想让该元素触发拖拽事件, 需要开启可拖拽属性
=> 给元素添加 draggable="true" 表示开启拖拽 -
整套的拖拽事件和两种元素关联
=> 拖拽元素: 你真的拖着他走的元素
=> 拖放元素: 你要松手的元素
拖拽元素身上的事件
1. dragstart 拖拽开始
注意: 鼠标在该元素上按下, 并且有移动的动作出现的瞬间
eleDiv.ondragstart = function () { console.log('你要拖拽') }
-
drag 拖拽过程
eleDiv.ondrag = function () { console.log('正在拖拽') } -
dragend 拖拽结束
eleDiv.ondragend = function () { console.log('拖拽结束了') }
拖放元素身上的事件
4. dragenter 拖拽元素进入拖放元素区域的时候触发
eleP.ondragenter = function () { console.log('进入拖放元素了') }
-
dragleave 拖拽元素离开拖放元素区域的时候触发
eleP.ondragleave = function () { console.log('离开拖放元素了') } -
ondragover 拖拽元素在拖放元素内移动的时候触发
eleP.ondragover = function () {
console.log('拖拽元素在拖放元素内移动')
return false
} -
drop 拖拽元素在拖放元素范围内结束拖拽的时候触发
注意: 需要在 dragover 事件内进行阻止默认行为
eleP.ondrop = function () { console.log('在拖放元素内放手') }
6.其他事件
其他事件
+ 指一些不好分类的事件
-
selectstart 框选开始
document.onselectstart = function () {
console.log('你竟然想复制内容')
return false
} -
transitionend 过渡结束
document.querySelector('p').ontransitionend = function () { console.log('过渡结束了')
} -
unload 你要关闭页面的时候
window.onunload = function () { window.localStorage.setItem('aaa', 100) }
7.案例 - 九宫格交换
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
ul {
width: 640px;
height: 640px;
border: 10px solid orange;
border-radius: 15px;
margin: 50px auto;
position: relative;
}
ul > li {
width: 200px;
height: 200px;
font-size: 30px;
font-weight: 700;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
color: #fff;
transition: all 1s linear;
}
</style>
</head>
<body>
<ul>
<li draggable="true" style="left: 10px; top: 10px; background-color: pink;">1</li>
<li draggable="true" style="left: 220px; top: 10px; background-color: skyblue;">2</li>
<li draggable="true" style="left: 430px; top: 10px; background-color: orange;">3</li>
<li draggable="true" style="left: 10px; top: 220px; background-color: purple;">4</li>
<li draggable="true" style="left: 220px; top: 220px; background-color: cyan;">5</li>
<li draggable="true" style="left: 430px; top: 220px; background-color: green;">6</li>
<li draggable="true" style="left: 10px; top: 430px; background-color: yellow;">7</li>
<li draggable="true" style="left: 220px; top: 430px; background-color: red;">8</li>
<li draggable="true" style="left: 430px; top: 430px; background-color: black;">9</li>
</ul>
<script>
/*
九宫格交换
需求:
+ 拖拽哪一个元素, 在哪一个元素上松手, 就让两个元素交换位置
*/
// 0. 获取元素
var lis = document.querySelectorAll('ul > li')
// 0. 准备变量
var startEle = null
// 1. 开始绑定事件
// 每一个元素都有可能是被拖拽元素, 每一个元素也都有可能是拖放元素
// 每一个元素需要绑定一个 拖拽开始事件(目的记录下 开始元素)
// 每一个元素需要绑定一个 松手事件(目的记录下 结束元素)
// 每一个元素需要绑定一个 dragover 事件(目的, 阻止默认行为, 好让松手事件触发)
lis.forEach(function (item) {
// 1-1. 每一个元素绑定一个 dragstart 事件
item.addEventListener('dragstart', function () {
// 只有在这里才能拿到开始元素
// 我们需要把这个开始元素记录下来
// 给 startEle 赋值
startEle = item
})
// 1-2. 每一个元素绑定一个 dragover 事件
item.ondragover = function () { return false }
// 1-3. 每一个元素绑定一个 drop 事件
item.ondrop = function () {
// console.log('松手了')
// 让 开始元素 和 结束元素 交换位置
// console.log('结束元素 : ', item)
// console.log('开始元素 : ', startEle)
// 交换 item 和 startEle 的位置
// 需要准备一个第三方变量临时接受一下
var tmpLeft = startEle.offsetLeft
var tmpTop = startEle.offsetTop
// 把当前交换的两个元素的层级提高
item.style.zIndex = 999
startEle.style.zIndex = 999
// 把 item 的值赋值给 startEle
startEle.style.left = item.offsetLeft + 'px'
startEle.style.top = item.offsetTop + 'px'
// 把 临时变量 记录的内容赋值给 item
item.style.left = tmpLeft + 'px'
item.style.top = tmpTop + 'px'
}
// 1-4. 每一个元素绑定一个 transitionend 事件
item.addEventListener('transitionend', function () {
item.style.zIndex = 1
})
})
</script>
</body>
2022.10.20
一.认识事件对象
事件对象
- 概念: 当事件触发的时候, 由浏览器提供的一个对象数据结构, 用于记录本次事件的所有相关信息
- 私人: 一个描述本次事件所有信息的对象数据结构
如何获取事件对象
-
在事件处理函数内直接书写一个形参
-
会在事件触发的时候, 有浏览器自动传递实参
-
该实参就是 事件对象
var ele = document.querySelector('div')
// 1.绑定事件
ele.onclick = function (abc) {
// 该形参会在该事件触发的时候, 由浏览器传递实参
console.log(abc)
}
二.事件对象内鼠标相关信息
事件对象内鼠标相关信息
光标的坐标位置
1. client 一套
+ 语法:<br>
=> 事件对象.clientX<br>
=> 事件对象.clientY<br>
+ 得到: 光标相对于浏览器可视窗口左上角的坐标位置
2. page 一套
+ 语法:<br>
=> 事件对象.pageX<br>
=> 事件对象.pageY<br>
+ 得到: 光标相对于文档流左上角的坐标位置
3. offset 一套
+ 语法:<br>
=> 事件对象.offsetX<br>
=> 事件对象.offsetY<br>
+ 得到: 光标相对 准确触发事件的元素 左上角的坐标位置
认识 JS 内的两大家族
-
offset 家族
-
offsetWidth 和 offsetHeight
=> 语法:
-> 元素.offsetWidth
-> 元素.offsetHeight
=> 得到: 该元素 内容 + padding + border 区域的尺寸 -
offsetLeft 和 offsetTop => 语法:
-> 元素.offsetLeft
-> 元素.offsetTop
=> 得到: 该元素相对于 定位父级 左边和上边的距离 -
offsetX 和 offsetY => 语法:
-> 事件对象.offsetX
-> 事件对象.offsetY
=> 得到: 光标相对于 准确触发事件的元素 左上角的坐标位置 -
offsetParent
=> 语法: 元素.offsetParent
=> 得到: 该元素的 定位父级, 也就是 offset 偏移量的参考元素
-
-
client 家族
-
clientWidth 和 clientHeight
=> 语法:
-> 元素.clientWidth
-> 元素.clientHeight
=> 得到: 该元素 内容 + padding 区域的尺寸 -
clientLeft 和 clientTop
=> 语法:
-> 元素.clientLeft
-> 元素.clientTop
=> 得到: 该元素 (内容+padding) 区域和 border 左上角的距离
-> 其实就是 左边框 和 上边框 的宽度 -
clientX 和 clientY
=> 语法:
-> 事件对象.clientX
-> 事件对象.clientY
=> 得到: 光标相对于 浏览器可视窗口左上角 的坐标位置
-
三.案例 - 列表鼠标跟随
<style>
* {
margin: 0;
padding: 0;
}
ul {
margin: 50px;
}
li {
list-style: none;
width: 600px;
height: 40px;
margin-bottom: 30px;
border: 1px solid #333;
position: relative;
}
li > p {
width: 300px;
height: 180px;
background-color: pink;
position: absolute;
left: 800px;
top: 300px;
display: none;
pointer-events: none;
z-index: 999;
}
</style>
<ul>
<li>我是标题1
<p style="background-color: pink;">我是解释内容1我是解释内容1我是解释内容1我是解释内容1我是解释内容1我是解释内容1我是解释内容1我是解释内容1我是解释内容1我是解释内容1我是解释内容1我是解释内容1</p>
</li>
<li>我是标题2
<p style="background-color: skyblue;">我是解释内容2</p>
</li>
<li>我是标题3
<p style="background-color: orange;">我是解释内容3我是解释内容3我是解释内容3我是解释内容3我是解释内容3我是解释内容3我是解释内容3我是解释内容3</p>
</li>
</ul>
<script>
/*
列表鼠标跟随
需求:
+ 光标在哪一个 li 内移动的时候, 哪一个 li 对应的 p 标签跟随光标移动
问题1: 什么时候出现效果 ?
=> 移入 li 的时候: mouseover
=> 移出 li 的时候: mouseout
=> 在 li 内移动的时候: mousemove
问题2: 在什么范围内 ?
=> li 身上
问题3: 什么效果 ?
=> 移入 li 的时候: 该 li 对应的 p 显示(display: block)
=> 移出 li 的时候: 该 li 对应的 p 隐藏(display: none)
=> 在 li 内移动的时候: 实时获取坐标点, 赋值给 li 对应的 p 的 left 和 top 样式
bug: 从左向右移动的时候, 会出现抖动问题 ?
=> 原因: 当你向右移动的时候, 光标会出现在 p 标签身上, 导致 li 和 光标之间有一个 p 标签出现
=> 解决:
-> 穿透
*/
// 0. 获取元素
var lis = document.querySelectorAll('ul > li')
// 1. 给每一个 li 绑定事件
lis.forEach(function (item) {
item.onmouseover = overHandler
item.onmouseout = outHandler
item.onmousemove = moveHandler
})
// 1-1. 移入的事件处理函数
function overHandler() {
// 当前移入的这个 li 的对应的 p 出现
// 问题: 你移入的是哪一个 li ? this
// 问题: 对应的 p 是谁 ? 你移入的这个 li 的子元素
// console.log(this.firstElementChild)
// 对应的 p 出现
this.firstElementChild.style.display = 'block'
}
// 1-2. 移出的事件处理函数
function outHandler() {
// 当前移出的这个 li 的对应的 p 隐藏
this.firstElementChild.style.display = 'none'
}
// 1-3. 移动的事件处理函数
function moveHandler(e) {
// 当前移动的这个 li 的对应的 p 跟随
// 拿到光标的坐标点
// 问题: 哪一组坐标点 ?
// 因为 p 是根据 li 来进行定位的
// 应该拿到一组相对于 li 左上角的坐标点
var x = e.offsetX + 8
var y = e.offsetY + 8
// 给对应 p 赋值
this.firstElementChild.style.left = x + 'px'
this.firstElementChild.style.top = y + 'px'
}
</script>
四.案例 - 拖拽元素
拖拽案例(1)
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 200px;
height: 200px;
background-color: skyblue;
position: absolute;
}
</style>
<div></div>
<script>
/*
案例 - 拖拽
需求:
+ 鼠标按下的时候, 可以跟着走
+ 随着鼠标移动, 让 div 跟随移动即可
+ 鼠标抬起的时候, 不能跟着走
问题1: 什么时候有效果 ?
+ 鼠标按下
+ 鼠标抬起
+ 鼠标移动
问题2: 在什么范围内有效果 ?
+ div 上按下
+ div 上抬起
+ document 内移动
问题3: 什么效果 ?
*/
// 0. 获取元素
var ele = document.querySelector('div')
// 0. 准备一个开关
var flag = false
// 绑定事件
ele.addEventListener('mousedown', function () {
// 开启开关
flag = true
})
ele.addEventListener('mouseup', function () {
// 关闭开关
flag = false
})
document.addEventListener('mousemove', function (e) {
// 函数内的代码, 不能随便执行
// 鼠标按下以后, 才可执行
// 一旦鼠标抬起以后, 代码不能执行
if (flag === false) return
// 实时获取坐标点
// 为了让光标显示在盒子的中心位置, 让盒子向左上移动
var x = e.clientX - ele.offsetWidth / 2
var y = e.clientY - ele.offsetHeight / 2
// 给 div 的 left 和 top 赋值
ele.style.left = x + 'px'
ele.style.top = y + 'px'
})
</script>
拖拽案例(2)
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 200px;
height: 200px;
background-color: skyblue;
position: absolute;
}
</style>
<div></div>
<script>
/*
案例 - 拖拽
需求:
+ 鼠标按下的时候, 可以跟着走
+ 随着鼠标移动, 让 div 跟随移动即可
+ 鼠标抬起的时候, 不能跟着走
需求:
+ 光标 和 被移动元素 的相对位置不变
+ 核心: 随着 mousemove 事件, 实时拿到 光标的 移动距离
=> 求出光标终点 - 起点的差值
=> 光标的起点: 按下的瞬间
=> 光标的终点: 移动的实时
+ 赋值: 给 div 盒子的 left 和 top 赋值为 初始位置 + 本次移动距离
=> 初始位置: 按下的瞬间
*/
// 0. 获取元素
var ele = document.querySelector('div')
// 0. 准备一个开关
var flag = false
// 准备变量表示开始坐标点
var startX = 0, startY = 0
// 准备变量表示初始位置
var startLeft = 0, startTop = 0
// 绑定事件
ele.addEventListener('mousedown', function (e) {
// 开启开关
flag = true
// 记录下按下时光标的坐标点
startX = e.clientX
startY = e.clientY
// 记录下盒子的初始位置
startLeft = ele.offsetLeft
startTop = ele.offsetTop
})
ele.addEventListener('mouseup', function () {
// 关闭开关
flag = false
})
document.addEventListener('mousemove', function (e) {
// 函数内的代码, 不能随便执行
// 鼠标按下以后, 才可执行
// 一旦鼠标抬起以后, 代码不能执行
if (flag === false) return
// 计算出光标的移动距离
var x = e.clientX - startX + startLeft
var y = e.clientY - startY + startTop
// 给 div 赋值
ele.style.left = x + 'px'
ele.style.top = y + 'px'
})
</script>
拖拽案例(3)边界值判断
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 200px;
height: 200px;
background-color: skyblue;
position: absolute;
}
</style>
<div></div>
<script>
/*
案例 - 拖拽
需求:
+ 鼠标按下的时候, 可以跟着走
+ 随着鼠标移动, 让 div 跟随移动即可
+ 鼠标抬起的时候, 不能跟着走
需求:
+ 光标 和 被移动元素 的相对位置不变
+ 核心: 随着 mousemove 事件, 实时拿到 光标的 移动距离
=> 求出光标终点 - 起点的差值
=> 光标的起点: 按下的瞬间
=> 光标的终点: 移动的实时
+ 赋值: 给 div 盒子的 left 和 top 赋值为 初始位置 + 本次移动距离
=> 初始位置: 按下的瞬间
需求:
+ 边界值判断, 不允许出界
+ 判断一下 x 和 y 的值
*/
// 0. 获取元素
var ele = document.querySelector('div')
// 0. 准备一个开关
var flag = false
// 准备变量表示开始坐标点
var startX = 0, startY = 0
// 准备变量表示初始位置
var startLeft = 0, startTop = 0
// 绑定事件
ele.addEventListener('mousedown', function (e) {
// 开启开关
flag = true
// 记录下按下时光标的坐标点
startX = e.clientX
startY = e.clientY
// 记录下盒子的初始位置
startLeft = ele.offsetLeft
startTop = ele.offsetTop
})
ele.addEventListener('mouseup', function () {
// 关闭开关
flag = false
})
document.addEventListener('mousemove', function (e) {
// 函数内的代码, 不能随便执行
// 鼠标按下以后, 才可执行
// 一旦鼠标抬起以后, 代码不能执行
if (flag === false) return
// 计算出光标的移动距离
var x = e.clientX - startX + startLeft
var y = e.clientY - startY + startTop
// 在 拿到 和 使用 之间, 对 x 和 y 进行判断
// 把 x 和 y 当做定位位置使用
// 左边: x 最小是 0
if (x <= 50) x = 0
// 上边: y 最小是 0
if (y <= 50) y = 0
// 右边: x 最大是 窗口宽度 - 盒子宽度
if (x >= document.documentElement.clientWidth - ele.offsetWidth - 50) x = document.documentElement.clientWidth - ele.offsetWidth
// 下边: y 最大是 窗口高度 - 盒子高度
if (y >= document.documentElement.clientHeight - ele.offsetHeight - 50) y = document.documentElement.clientHeight - ele.offsetHeight
// 给 div 赋值
ele.style.left = x + 'px'
ele.style.top = y + 'px'
})
</script>
五.事件对象内键盘相关信息
事件对象内键盘相关信息
+ 按下的是哪一个按键
+ 按下的是否是组合按键
按下的按键:
+ 语法: 事件对象.keyCode
按下的按键(标准浏览器内才有):
+ 语法: 事件对象.key
按下的是否是组合按键
+ 事件对象内有四个成员
=> shiftKey
=> ctrlKey
=> altKey
=> metaKey(win: win, mac: command)
+ 默认值是 false
=> 当按下的时候, 该值是 true
var inp = document.querySelector('input')
inp.onkeydown = function (e) {
// 按下的是哪一个按键
// console.log(e.keyCode)
// if (e.keyCode === 27) {
// inp.value = ''
// }
// 按下的按键
// console.log(e.key)
// 判断是组合按键
if (e.shiftKey === true && e.ctrlKey === true && e.altKey === true && e.keyCode === 65) {
console.log('你按下的是 shift + ctrl + alt + a')
}
}
六.案例 - 打字游戏
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.map {
width: 800px;
height: 400px;
border: 3px solid orange;
margin: 50px auto;
position: relative;
overflow: hidden;
}
.map > .word {
width: 20px;
height: 20px;
background-color: skyblue;
border-radius: 50%;
line-height: 20px;
text-align: center;
position: absolute;
top: -20px;
}
button {
display: block;
width: 200px;
height: 30px;
background-color: lightcoral;
margin: 30px auto;
cursor: pointer;
}
</style>
</head>
<body>
<button>开始游戏</button>
<div class="map"></div>
<div class="score">
目前的成绩是 : <h2></h2>
</div>
<script src="./utils.js"></script>
<script>
/*
案例 - 打字游戏
*/
// 0. 获取元素
var map = document.querySelector('.map')
var scoreBox = document.querySelector('.score > h2')
var startBtn = document.querySelector('button')
// 0. 准备变量
var str = 'abcdefghijklmnopqrstuvwxyz'
// 准备变量表示总定时器返回值
var t = 0
// 准备一个变量表示成绩
var score = 0
// 1. 准备一个函数
// 能生成一个 "字", 并且放在指定(top: -20px; left: 横向随机)的位置
function createWord() {
// 1-1. 创建一个 div 标签
var word = document.createElement('div')
// 1-2. 设置 字 的样式
word.className = 'word'
// 1-3. 设置 字 的 left 值
word.style.left = randomNum(0, map.offsetWidth - 20) + 'px'
// 1-4. 设置 字 里面的 文本内容
word.innerText = str[ randomNum(0, str.length - 1) ]
// 设置随机背景颜色
// word.style.backgroundColor = randomColor()
// 1-5. 把这个 字 插入到 页面内
map.appendChild(word)
// 1-6. 开启一个定时器让他落下来
var timer = setInterval(function () {
// 拿到目前的偏移量 + 固定值
var direction = word.offsetTop + 10
// 再次赋值给 字
word.style.top = direction + 'px'
// 走到一个指定位置得 停下来
if (direction >= map.offsetHeight) {
// 让 map 内的所有正在下落的字停下来
// 拿到 map 内所有的子元素, 拿到每一个的自定义属性 timer, 依次去关闭
var words = [ ...map.children ]
words.forEach(function (item) {
clearInterval(item.dataset.timer - 0)
})
// 关闭总定时器
clearInterval(t)
// 提示结束游戏
if (window.confirm('游戏结束, 是否重新开始游戏')) {
// 代码执行到这里, 说明用户点击的是确定
window.location.reload()
}
}
}, 100)
// 1-7. 把每一个字的定时器返回值, 以自定义属性的形式书写在标签身上
word.dataset.timer = timer
}
// 2. 每间隔一段时间, 下落一个 字
function create() {
t = setInterval(function () {
createWord()
}, 500)
}
// 3. 准备一个函数, 绑定键盘事件
function bindEvent() {
document.addEventListener('keydown', function (e) {
// 3-1. 每一次按下键盘, 拿到本次按下的内容
var key = e.key
// 3-2. 找到 map 的所有子元素内, 是否有一样的
var words = [ ...map.children ]
var info = words.find(function (item) { return item.innerText === key })
// info 要么是一个 元素
// info 要么是一个 undefined
// 如果 !info 是一个 true, 说明 info 本身是一个 false
// info 只有是 undefined 的时候, 本身才是 false
// 判断如果没有, 什么也不用做
if (!info) return
// 如果有东西, 把当前这个定时器关掉
clearInterval(info.dataset.timer - 0)
// 把 info 移除页面
info.remove()
// 成绩 ++
score++
// 把成绩写入到标签内
scoreBox.innerText = score
})
}
// 4. 点击按钮开始游戏
startBtn.onclick = function () {
create()
bindEvent()
// 一旦开始游戏, 让该按钮 隐藏或者禁用
startBtn.disabled = true
}
</script>
2022.10.22
一.事件传播
概念: 当行为发生的时候, 会按照 结构父级 的顺序, 依次向上传递该 事件行为
window先接收传给document传给html传给 事件目标
问题1: 如果 inner 元素身上没有绑定 click 事件, 点击 inner 的时候, center 级后续的元素是否还会触发事件 ?
+ 会触发, 传递的是事件行为
问题2: 如果给中间某一层没有 click 事件, 点击 inner 的时候, 是否还会继续传递到 window ?
+ 会, 传递的是事件行为
问题3: 把 inner 定位到 outer 的外面, 点击 inner 的时候, 是否还会传递到 center 和 outer ?
+ 会, 因为事件传播是按照结构父级的顺序传播
问题4: 当 p 定位到 center 内部的时候, 点击 p 的时候, 是否会传递到 center 和 outer 身上 ?
+ 不会, 因为事件传播是按照结构父级的顺序传播
二.事件流机制
事件流机制
+ 指: 事件的 捕获 目标 和 冒泡
+ 事件捕获: 在事件流中, 从 window 到 事件目标 的传递过程
+ 事件目标: 准确触发事件的元素
+ 事件冒泡: 在事件流中, 从 事件目标 到 window 的传递过程
事件流
+ 在 IE 低版本浏览器内, 只能在 冒泡阶段 触发事件, 不能在 捕获阶段 触发事件
+ 在标准浏览器中, 默认在 冒泡阶段 触发事件, 可选在 捕获阶段 触发事件
如何在捕获阶段触发事件(了解)
+ 必须是 DOM 2级 事件
+ 事件源.addEventListener('事件类型', 事件处理函数, 冒泡还是捕获)
=> 默认值是 false, 表示冒泡
=> 选填值是 true, 表示捕获
window > document >html >body > div > p
现在点击p p就是 事件目标
window-> document -> html -> body ->div ->p的过程是事件捕获
p -> div ->body ->html ->document->window 是事件冒泡
三.阻止事件传播
阻止事件传播
+ 语法:
=> 标准浏览器: 事件对象.stopPropagation()
=> IE 低版本: 事件对象.cancelBubble = true
案例-网页换肤
需求:
1. 点击 更换皮肤 按钮, ul 出现
2. 点击 ul 内的 span, 让 ul 隐藏
3. 点击 ul 内的每一个 img, 更换 body 的 backgroundImage 样式(ul 不能消失)
4. 点击 网页其他 空白位置, 让 ul 隐藏
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
img {
width: 100%;
height: 100%;
display: block;
cursor: pointer;
}
ul {
width: 900px;
height: 430px;
border: 5px solid #333;
border-radius: 15px;
background-color: #fff;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
display: flex;
align-items: center;
justify-content: space-evenly;
display: none;
}
ul > li {
width: 280px;
height: 200px;
}
ul > span {
font-size: 30px;
font-weight: 700;
position: absolute;
right: 30px;
top: 10px;
cursor: pointer;
}
button {
padding: 5px 15px;
font-size: 20px;
}
</style>
<button>更换皮肤</button>
<ul class="box">
<span>X</span>
<li><img draggable="false" src="./imgs/001.webp" alt=""></li>
<li><img draggable="false" src="./imgs/002.webp" alt=""></li>
<li><img draggable="false" src="./imgs/003.webp" alt=""></li>
<button>上一页</button>
<button>下一页</button>
</ul>
<script>
// 0. 获取元素
var btn = document.querySelector('button')
var box = document.querySelector('.box')
var closeBtn = box.querySelector('span')
var imgs = box.querySelectorAll('img')
// 1. 点击 更换皮肤 按钮, ul 出现
btn.onclick = function (e) {
// 阻止事件传播
e.stopPropagation()
// 让 ul 出现
box.style.display = 'flex'
}
// 2. 点击 ul 内的 span, 让 ul 隐藏
closeBtn.onclick = function () {
// 让 ul 隐藏
box.style.display = 'none'
}
// 3. 点击 ul 内的每一个 img, 更换 body 的 backgroundImage 样式(ul 不能消失)
imgs.forEach(function (item) {
// 给每一个 img 标签绑定事件
item.onclick = function () {
// 拿到当前点击的这个 img 标签的 src 属性, 赋值给 body 的 backgroundImage
var link = this.src
document.body.style.backgroundImage = `url(${ link })`
}
})
// 4. 点击 网页其他 空白位置, 让 ul 隐藏
document.onclick = function () {
// 让 ul 隐藏
box.style.display = 'none'
}
// 5. 在 ul 的 click 事件内, 阻止事件传播
box.onclick = function (e) {
e.stopPropagation()
}
</script>
四.事件委托
事件委托
+ 利用事件传播的机制, 来实现某些效果
+ 指: 把子元素的事件委托给父元素来绑定, 在父元素的事件内, 通过事件目标来判断你准确触发事件的元素
如何拿到本次事件的事件目标
+ 语法: 事件对象.target
+事件对象.target.tagName 拿到的标签名是全大写
循环绑定事件
+ 缺点1: 对于后期动态操作的元素不够友好
+ 缺点2: 大量的 DOM 操作
事件委托
+ 优点1: 对于后期动态操作的元素足够友好
+ 优点2: 减少 DOM 操作
五.阻止默认行为
默认行为
+ 不需要你进行事件绑定, 天生自带的一些行为
+ a 标签的点击行为
+ form 标签的表单提交
+ 鼠标右键的单击行为
+ ...
阻止默认行为
+ 注意: 需要在同类型事件内进行阻止
+ 标准浏览器: 事件对象.preventDefault()
+ IE 低版本: 事件对象.returnValue = false
+ 通用: return false
六.案例 - 本地模拟购物车
css部分
* {
margin: 0;
padding: 0;
}
.container {
width: 1200px;
margin: 0 auto;
}
.header, .footer {
background-color: skyblue;
color: #fff;
font-weight: 700;
font-size: 50px;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
}
.footer {
height: 300px;
}
.content {
margin: 10px auto;
}
.content > .empty {
height: 600px;
display: flex;
justify-content: center;
align-items: center;
}
.content > .full {
display: flex;
flex-direction: column;
font-size: 20px;
}
.content > .full > .top,
.content > .full > .bottom {
height: 40px;
background-color: orange;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-sizing: border-box;
}
.content > .full > .top {
justify-content: flex-start;
}
.content > .full > .top > input {
width: 30px;
height: 30px;
margin: 0 15px;
}
.content > .full > .bottom > .btns > button {
font-size: 20px;
padding: 0 10px;
cursor: pointer;
}
.content > .full > .bottom > .totalPrice > span {
font-weight: 700;
font-size: 24px;
color: red;
}
.content > .full > .center {
padding-top: 10px;
}
.content > .full > .center > li {
width: 100%;
height: 100px;
margin-bottom: 10px;
border: 1px solid #333;
box-sizing: border-box;
list-style: none;
display: flex;
}
.content > .full > .center > li > div {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
border-right: 1px solid #333;
}
.content > .full > .center > li > div:last-child {
border: none;
}
.content > .full > .center > li > .select,
.content > .full > .center > li > .show,
.content > .full > .center > li > .destory {
width: 100px;
}
.content > .full > .center > li > .price,
.content > .full > .center > li > .number,
.content > .full > .center > li > .subPrice {
width: 200px;
}
.content > .full > .center > li > .title {
flex: 1;
box-sizing: border-box;
padding: 5px;
justify-content: flex-start;
align-items: flex-start;
}
.content > .full > .center > li > .price > span,
.content > .full > .center > li > .subPrice > span {
color: red;
font-size: 22px;
font-weight: 700;
}
.content > .full > .center > li > .destory > button {
padding: 5px 10px;
background-color: red;
color: #fff;
border: none;
outline: none;
cursor: pointer;
}
.content > .full > .center > li > .number > button {
width: 30px;
height: 30px;
cursor: pointer;
}
.content > .full > .center > li > .number > input {
width: 50px;
border: none;
outline: none;
text-align: center;
font-size: 20px;
}
.content > .full > .center > li > .show > img {
width: 100%;
height: 100%;
display: block;
}
html部分
<div class="header container">啥都有 · 购物车</div>
<div class="content container">
<!-- 空购物车的状态 -->
<!-- <div class="empty">
<img src="https://img2.baidu.com/it/u=2075058702,1217821591&fm=253&app=138&size=w931&n=0&f=PNG&fmt=auto?sec=1666458000&t=76fe109491811f29b7b6b9ad44301195" alt="">
</div> -->
<div class="full">
<div class="top">
全选 : <input type="checkbox">
</div>
<ul class="center">
<li>
<div class="select">
<input type="checkbox">
</div>
<div class="show">
<img src="https://img0.baidu.com/it/u=3414885164,1584682578&fm=253&fmt=auto&app=120&f=JPEG?w=200&h=200" alt="">
</div>
<div class="title">
商品介绍
</div>
<div class="price">
<span>¥ 100.00</span>
</div>
<div class="number">
<button>-</button>
<input type="text" value="1">
<button>+</button>
</div>
<div class="subPrice">
<span>¥ 100.00</span>
</div>
<div class="destory">
<button>删除</button>
</div>
</li>
</ul>
<div class="bottom">
<div class="total">
总计 : 0 件商品
</div>
<div class="btns">
<button>清空购物车</button>
<button>去支付</button>
<button>删除所有已选中商品</button>
</div>
<div class="totalPrice">
总价 : ¥ <span>100.00</span>
</div>
</div>
</div>
</div>
<div class="footer container">网页底部</div>
JS部分
// 0. 准备数据
var cart = [{ id: 1, title: '我是一个手机', price: 100, buy: 2, number: 16, is_select: true, pic: 'https://img0.baidu.com/it/u=3414885164,1584682578&fm=253&fmt=auto&app=120&f=JPEG?w=200&h=200' },{ id: 2, title: '我是一个电脑', price: 33.56, buy: 1, number: 13, is_select: false, pic: 'https://img0.baidu.com/it/u=52910157,628543061&fm=253&fmt=auto&app=120&f=JPEG?w=200&h=200' },{ id: 3, title: '我是一个二级', price: 78.92, buy: 3, number: 8, is_select: true, pic: 'https://img1.baidu.com/it/u=486689227,4095944776&fm=253&fmt=auto&app=120&f=JPEG?w=200&h=200' } ]
// cart = []
// 0. 获取元素
var contentBox = document.querySelector('.content')
// 1. 渲染页面
bindHtml()
function bindHtml() {
// 如果 cart.length 不为 0, 说明购物车有内容, 需要渲染的 full 标签
// 如果 cart.length 为 0, 说明购物车没有内容, 需要渲染的 empty 标签
if (!cart.length) {
// 说明是空的把
// 代码能执行到这里, 说明 !cart.length 是一个 true
// 说明 cart.length 本身是 false
contentBox.innerHTML = `
<div class="empty">
<img src="https://img2.baidu.com/it/u=2075058702,1217821591&fm=253&app=138&size=w931&n=0&f=PNG&fmt=auto?sec=1666458000&t=76fe109491811f29b7b6b9ad44301195" alt="">
</div>
`
return
}
// 代码能执行到这里, 说明 cart.length 不为 0
// 全选按钮是否选中
// 如果所有数据中 is_select 都是 true, 那么有 checked
// 只要任何一条数据的 is_select 是 false, 那么没有 checked
// 总件数
// 统计所有 is_select 为 true 的数据的 buy 的 和
// 总价格
// 统计所有 is_select 为 true 的数据的 price 的 和
var totalBuy = 0, totalPrice = 0, totalSelect = 0
cart.forEach(function (item) {
// 什么情况下才 叠加, is_select 为 true
if (item.is_select) {
totalSelect++ // 叠加有多少种选中的
totalBuy += item.buy // 统计总购买数量
totalPrice += item.price * item.buy // 统计总购买价格
}
})
var str = `
<div class="full">
<div class="top">
全选 : <input class="toggle-all" type="checkbox" ${ cart.length === totalSelect ? 'checked' : '' }>
</div>
<ul class="center">
`
// 根据 cart 来决定渲染多少个 li
cart.forEach(function (item) {
str += `
<li>
<div class="select">
<input data-id="${ item.id }" class="toggle" type="checkbox" ${ item.is_select ? 'checked' : '' }>
</div>
<div class="show">
<img src="${ item.pic }" alt="">
</div>
<div class="title">
${ item.title }
</div>
<div class="price">
<span>¥ ${ item.price.toFixed(2) }</span>
</div>
<div class="number">
<button data-id="${ item.id }" class="sub">-</button>
<input type="text" value="${ item.buy }">
<button data-id="${ item.id }" class="add">+</button>
</div>
<div class="subPrice">
<span>¥ ${ (item.price * item.buy).toFixed(2) }</span>
</div>
<div class="destory">
<button data-id="${ item.id }" class="del">删除</button>
</div>
</li>
`
})
str += `
</ul>
<div class="bottom">
<div class="total">
总计 : ${ totalBuy } 件商品
</div>
<div class="btns">
<button class="clear">清空购物车</button>
<button class="pay" ${ totalSelect === 0 ? 'disabled' : '' }>去支付</button>
<button class="clear-completed" ${ totalSelect === 0 ? 'disabled' : '' }>删除所有已选中商品</button>
</div>
<div class="totalPrice">
总价 : ¥ <span>${ totalPrice.toFixed(2) }</span>
</div>
</div>
</div>
`
contentBox.innerHTML = str
}
// 2. 绑定事件
// 发现:
// 所有需要操作的事件都是 click 行为
// 并且有共同的父级结构, contentBox
// 所有元素 click 的时候都会 冒泡 传递到 contentBox 身上
// 事件委托
contentBox.addEventListener('click', function (e) {
// 2-1. 判断点击的是 全选
if (e.target.className === 'toggle-all') {
// 我自己的状态是什么, 就需要给 cart 中所有数据的 is_select 设置为什么
var state = e.target.checked
// 遍历 cart 赋值
cart.forEach(function (item) { item.is_select = state })
// 重新渲染页面
bindHtml()
}
// 2-2. 判断点击的是 清空购物车
if (e.target.className === 'clear') {
// 把 cart 内的所有内容都删除
cart.length = 0
// 重新渲染页面
bindHtml()
}
// 2-3. 判断点击的是 去支付
if (e.target.className === 'pay') {
window.location.href = 'https://www.jd.com'
}
// 2-4. 判断点击的是 删除所有已选中商品
if (e.target.className === 'clear-completed') {
// 把 cart 内所有 is_select 为 true 的删除掉
// 把 cart 内所有 is_select 为 false 的留下
// 利用 filter 方法
cart = cart.filter(function (item) { return item.is_select === false })
// 重新渲染页面
bindHtml()
}
// 2-5. 判断点击的是 每一项的 选中按钮
if (e.target.className === 'toggle') {
// 问题: 你要修改哪一条数据 ?
// 在你渲染页面的时候, 把 该数据的 id 渲染在 标签身上
// 点击的时候, 拿到 你点击的这个元素 身上一个叫做 data-id 的自定义属性的值, 就知道修改哪一个数据了 ?
var id = e.target.dataset.id - 0
// 从 cart 数组内找到 id 对应的那一条数据
var info = cart.find(function (item) { return item.id === id })
// 修改数据
info.is_select = !info.is_select
// 从新渲染页面
bindHtml()
}
// 2-6. 判断点击的是 每一项的 删除按钮
if (e.target.className === 'del') {
// 拿到要删除的数据的 id
var id = e.target.dataset.id - 0
// 从 cart 内删除 id 对应的数据, 其实就是把 id 不一样的留下
cart = cart.filter(function (item) { return item.id !== id })
// 从新渲染页面
bindHtml()
}
// 2-7. 判断点击的是 每一项的 增加按钮
if (e.target.className === 'add') {
// 拿到要修改的数据的 id
var id = e.target.dataset.id - 0
// 从 cart 内找到对应的数据
var info = cart.find(function (item) { return item.id === id })
// 判断是不是真的要 +
if (info.buy >= info.number) return
// 购买数量 ++
info.buy++
// 从新渲染页面
bindHtml()
}
// 2-8. 判断点击的是 每一项的 减少按钮
if (e.target.className === 'sub') {
// 拿到要修改的数据的 id
var id = e.target.dataset.id - 0
// 从 cart 内找到 id 对应的数据
var info = cart.find(function (item) { return item.id === id })
// 判断是否真的要 减少
if (info.buy <= 1) return
// 操作购买数量减少
info.buy--
// 从新渲染页面
bindHtml()
}
})
2022.10.24
一.this指向
this 指向
+ 是一个使用在作用域内的关键字
+ 要么使用在全局作用域
=> 比较少使用在全局
=> 使用在全局的时候, this 指向 window
+ 要么使用在私有作用域(函数内)
=> 变化比较大, 用处比较多
函数内的 this 指向(熟读并背诵全文)
+ 核心: 不管函数怎么定义, 不管函数在哪定义, 只看函数的调用方式(箭头函数除外)
+ 总结:
1. 基本调用(普通调用)
=> 函数名()
=> this 指向 window
2. 对象调用
=> 对象名.函数名()
=> this 指向 点前面的内容(点前面是谁就是谁)
3. 定时器调用
=> setTimeout(函数, 数字)
=> setInterval(函数, 数字)
=> this 指向 window
4. 事件处理函数调用
=> 事件源.on事件类型 = 函数
=> 事件源.addEventListener('事件类型', 函数)
=> this 指向 事件源(绑定在谁身上的事件)
5. 未完待续...
二.强行改变this指向
强行改变 this 指向
+ 不管你本身指向哪里, 我让你指向哪里就得指向哪里
1. call
=> 语法: 直接连接在函数名后面使用
-> 函数名.call()
-> 对象名.函数名.call()
=> 参数:
-> 第一个参数: 该函数内的 this 指向
-> 第二个参数开始: 依次给该函数进行形参赋值
=> 特点: 立即调用函数
2. apply
=> 语法: 直接连接在函数名后面使用
-> 函数名.apply()
-> 对象名.函数名.apply()
=> 参数:
-> 第一个参数: 该函数内的 this 指向
-> 第二个参数: 是一个数组或者伪数组都行, 按照顺序依次给该函数进行形参赋值
=> 特点: 立即调用函数
3. bind
=> 语法: 直接连接在函数名后面使用
-> 函数名.bind()
-> 对象名.函数名.bind()
=> 参数:
-> 第一个参数: 该函数内的 this 指向
-> 第二个参数开始: 依次给该函数进行形参赋值
=> 特点: 不会立即调用函数
-> 而是返回一个一模一样的新函数
-> **一个锁死了 this 指向的新函数**
通常你需要立即调用的函数, 使用 call 和 apply 来改变 this
你需要不是立即调用的函数(定时器处理函数, 事件处理函数), 使用 bind 来改变 this
三.认识ES6
认识 ES6
+ ECMAScript 语法的发展过程中的一个版本
+ 官方: ES2015
+ 一些语法上的更新和迭代
1.ES6 定义变量
+ 在 ES6 新增了两个定义变量的关键字
=> let 变量
=> const 常量
let/const 和 var 的区别
1. 预解析
=> var 有预解析, 可以先使用后定义
=> let/const 没有预解析, 必须先定义后使用
2. 重复变量名
=> var 可以定义重复变量名
=> let 和 const 不允许重复变量名
3. 块级作用域
=> var 没有块级作用域, 只有函数能限制其使用范围
=> let/const 有块级作用域, 任何一个可以执行代码的大括号, 都能限制其使用范围
let 和 const 的区别
4. 修改值的问题
=> let 定义的变量值可以修改
=> const 定义的常量值不可以被修改
5. 初始化赋值的问题
=> let 可以在初始化的时候不赋值
=> const 初始化的时候必须赋值, 并且一经赋值不能修改
变量命名规范
+ 定义的过程中, 能用 常量 尽量使用 常量
+ 常量的定义过程中, 字母全大写
2.ES6 的箭头函数
ES6 的箭头函数
+ ES6 语法中对 函数表达式(赋值式函数) 的简写形式
=> var fn = function () {}
=> setTimeout(function () {}, 1000)
=> xxx.onclick = function () {}
=> xxx.forEach(function () {})
=> ...
+ 语法: () => {}
() 书写形参的位置
=> 箭头函数的标志
{} 书写函数体的位置
箭头函数的特点:
1. 可以省略 小括号 不写
=> 当你的形参只有一个的时候, 可以省略小括号不写
2. 可以省略 大括号 不写
=> 当你的代码只有一句话的时候, 可以省略大括号不写
=> 并且该函数会自动把这一句话的结果当做该函数的返回值
3. 箭头函数没有 this
=> 箭头函数内的 this 就是外部作用域的 this
=> 私人: 你把该函数删除, this 是谁, 加上箭头函数 this 就是谁
4. 箭头函数没有 arguments
5. 箭头函数不能当做构造函数使用
3.ES6 的函数参数默认值
/*
ES6 的函数参数默认值
+ 给函数的形参设置一个默认值, 当你没有传递实参的时候, 直接使用默认值
+ 如果你传递了实参, 那么就使用你传递的
+ 实现: 在定义函数的时候, 直接给形参使用 赋值符号(=) 赋值即可
*/
// 函数参数默认值
function fn(a = 10, b = 20) {
// 当你没有给形参 a 赋值的时候, a 就是 10
// 当你没有给形参 b 赋值的时候, b 就是 20
console.log('a : ', a)
console.log('b : ', b)
}
fn()
fn(100)
fn(1000, 2000)
4.ES6 的模板字符串
/*
ES6 的模板字符串
+ 在 ES6 语法中, 以 反引号(``) 来定义字符串
+ 特点1: 可以换行书写
+ 特点2: 可以直接解析变量, 当你需要解析变量的时候, 可以书写 ${ 变量 }
*/
5.ES6 的解构赋值
ES6 的解构赋值
+ 目的: 快速从数组或者对象中获取成员
+ 解构数组
+ 解构对象
/*
解构数组
+ 语法: var 解构 = 数组
var [ 变量1, 变量2, ... ] = 数组
+ 按照索引, 分别对该变量进行赋值
+ 多维数组解构
=> 数组怎么书写, 解构怎么书写, 数据换成变量
*/
// 解构数组
var arr = [ 100, 200, 300, 400 ]
var a = arr[0]
var b = arr[1]
var c = arr[2]
var d = arr[3]
// 尝试解构
var [ a, b, c ] = arr
console.log(a, b, c)
// 多维数组解构
var arr = [ 100, 200, [ 300, 400, [ 500, 600, [ 700, 800, [ 900 ] ] ] ] ]
var [ a, b, [ c, d, [ e, f, [ g, h, [ i ] ] ] ] ] = arr
console.log(e, i)
/*
解构对象
+ 语法: var 解构 = 对象
var { 同名key, ... } = 对象
+ 得到: 按照 同名key 去定义变量并且获取对象中的成员
解构对象时候起一个别名
+ 语法: var { 同名key: 别名 } = 对象
解构多维对象
+ 对象怎么写, 解构怎么写, 把值去掉即可
*/
// 解构对象
var obj = { name: 'Jack', age: 18, gender: '男' }
var name = obj.name
var age = obj.age
var gender = obj.gender
console.log(name, age, gender)
// 尝试解构
// 等价于 var age = obj.age
// 定义了一个叫做 age 的变量, 获取的是 obj 内一个叫做 age 的成员的值
var { gender, age, name, classRoom } = obj
console.log(age, name, gender, classRoom)
// 别名
var n = obj.name
console.log(n)
// 定义了一个叫做 n 的变量, 获取的值是 obj 内叫做 n 的 key 的值
var { n } = obj
// 定义了一个叫做 n 的变量, 获取的值是 obj 内叫做 name 的 key 的值
// 等价于 var n = obj.name
var { name: n } = obj
console.log(n)
var { age: a } = obj
console.log(a)
console.log(age)
var { name: n } = obj
console.log(n)
console.log(name)
// 解构多维对象
var obj = {
name: 'Jack',
age: 18,
info: {
weight: 180,
height: 180,
desc: {
message: '你好世界',
city: {
address: '北京'
}
}
},
hobby: [ '足球', '篮球', '羽毛球' ]
}
// 尝试解构
var {
name,
age,
info: {
weight,
height,
desc: {
message,
city: {
address: ad
}
}
},
hobby: [ a, b, c ]
} = obj
console.log(ad)
6.ES6 的对象简写语法
/*
ES6 的对象简写语法
+ ES6 语法内对于书写对象的简写形式
1. 当 key 和 value 一模一样的时候, 并且 value 是一个变量的时候
=> 可以省略一个不写
2. 当 value 值是一个 函数, 并且不是箭头函数的时候
=> 可以省略 function 关键字和 冒号(:) 不写
*/
// 简写1:
// var day = 12
// var hours = 13
// var minutes = 14
// var seconds = 15
// var obj = {
// day, // 等原与 day: day
// hours,
// minutes,
// seconds,
// week: 'week'
// }
// console.log(obj)
// 简写2:
// var obj = {
// f1: function () {}, // 这样的函数可以简写
// f2: () => {
// console.log(this)
// }, // 这个函数不能在简写了
// f3 () {
// console.log(this)
// }
// }
// obj.f3()
// obj.f2()
7.ES6 的扩展运算符 (...)
+ 含义1: 展开(数据结构内, 函数的实参)
+ 含义2: 合并(函数的形参, 解构位置)
// 展开
// var a1 = [ 100, 200 ]
// var a2 = [ 300, 400 ]
// var arr = [ ...a1, '随便', ...a2 ]
// console.log(arr)
// var o1 = { name: 'Jack' }
// var o2 = { name: 'Rose', age: 18 }
// var obj = {
// ...o1,
// ...o2,
// name: '张三'
// }
// console.log(obj)
// function fn(a, b, c) {
// console.log(a, b, c)
// }
// var arr = [ 100, 200, 300 ]
// fn(...arr)
// 合并
// 在形参中使用 合并 的时候
// 该运算符必须要写在最后一个形参位置, 而且只能使用一次
// function fn(a, c, ...b) {
// // 把第一个实参赋值给了 a
// // 把从第二个实参开始到末尾的所有实参, 放在一个数组内给到 b
// console.log(a)
// console.log(c)
// console.log(b)
// }
// fn(100, 200, 300, 400, 500, 600)
// var arr = [ 100, 200, 300, 400, 500, 600 ]
// 在解构中使用 合并 的时候
// 该运算符必须要写在最后一个形参位置, 而且只能使用一次
// var [ a, c, ...b ] = arr
// console.log(a)
// console.log(c)
// console.log(b)
8.ES6 的模块化开发
/*
ES6 的模块化开发
+ 指: 把一个完整逻辑, 按照功能拆开成为一部分一部分的内容, 在合并起来
小问题:
+ 按照什么来区分的模块 ? 文件
=> 每一个 js 文件叫做一个 独立的文件模块
+ 当按照模块化语法开始书写代码的时候
=> 每一个 js 文件都相当于一个 独立的 模块作用域(文件作用域)
=> 写在 a.js 文件内的内容, 不能在 b.js 文件内使用的
=> 问题: 多个文件之间的内容不互通
导入 / 导出
+ 目的: 就是为了 a.js 文件内书写的内容可以在 b.js 文件内使用
导出
+ 在当前文件内进行导出的时候
+ 把该文件内的内容向外暴露
+ 语法1:
=> export default 数据
=> 在一个文件内只能使用一次, 而且只能导出一个数据
+ 语法2:
=> export 定义变量 = 值
=> 在一个文件内可以使用多次
导入
+ 当你导入另一个 js 文件的时候, 接受一下该文件导出的内容
+ 语法1:
=> import 变量名 from '文件地址'
=> 变量 接受的就是 该 文件地址 内导出的内容
+ 语法2:
=> import { 变量, ... } from '文件地址'
=> 按照该文件地址内导出的内容去接受内容
注意: 如果你想使用模块化语法
+ script 标签必须有一个 type 属性值为 module
+ 当前页面必须在 服务器 上打开, 本地打开不能使用(live server)
+ 导出语法1 对应 导入语法1
+ 导出语法2 对应 导入语法2
+合并导入语法:
import *as 变量名 from '文件地址'
import 变量名 ,{变量名,....} from '文件地址'
*/
2022.10.26
一.认识正则表达式
认识正则表达式
+ 正则也是 JS 内的一种数据类型, 是一个复杂数据类型
+ 叫做 Regular Expression
+ 作用: 自己书写一个规则, 来 验证字符串是否符合规则 或者 从一段字符串内获取一段满足要求的字符串片段
创建正则表达式
1. 字面量方式创建
=> 语法: var reg = /符号/
2. 内置构造函数方式创建
=> 语法: var reg = new RegExp('符号')
两种创建方式的区别
1. 语法书写
=> 字面量: var reg = /元字符/标识符
=> 内置构造函数: var reg = new RegExp('元字符', '标识符')
2. 字符串拼接
=> 字面量不接受拼接字符串
=> 内置构造函数接受拼接字符串(因为第一个参数就是以字符串的形式传入正则符号)
3. 书写基础元字符
=> 字面量直接书写基础元字符即可, \s\d\w
=> 内置构造函数方式需要书写双斜线 \\s\\d\\w
二.正则表达式的常用方法
1. 匹配
+ 作用: 验证字符串是否符合规则
+ 语法: 正则.test(字符串)
+ 返回值: 一个布尔值
=> true, 说明该字符串符合正则规则
=> false, 说明该字符串不符合正则规则
2. 捕获
+ 作用: 从完整字符串内捕获出一段符合规则的字符串片段
+ exec 捕获
+ 作用: 从完整字符串内捕获出符合要求的字符串片段
+ 语法: 正则.exec(字符串)
+ 返回值:
=> 字符串内没有符合正则规则的字符串片段, 此时返回值就是 null
=> 字符串内有符合正则规则的字符串片段
-> 返回值必然是一个数组
-> [0] 位置就是找到的第一个满足要求的字符串片段
-> 只能找到第一个满足要求的字符串片段
-> 每一次的检测, 都是从 [0] 开始向后检索
-> 标识符 g 的作用
+ 如果正则没有全局标识符 g, 那么每一次的检测都是从 [0] 开始检索
+ 如果正则有全局标识符 g, 那么第二次检索是从第一次的结束位置开始向后检索, 直到找不到为止, 返回 null, 再下一次检索又从 [0] 开始检索
-> () 的作用
+ 如果没有 (), 那么 [1] 开始不会有内容
+ 如果有 (), 那么从 [1] 开始依次是每一个小括号内的内容
扩展
+ 匹配但不捕获
=> 当你指向要 小括号的 整体作用, 不需要单独捕获的时候
=> 书写 (?:)
+ 起名小分组
=> (?<变量名>)
=> 会在返回值的 groups 内单独添加一个该成员
三.基本元字符
正则的组成
+ 符号 和 文本
+ 符号:
=> 元字符(基础元字符 / 边界符 / 限定符 / ...)
=> 标识符
基本元字符
+ \d 表示 一位 数字(0-9)
+ \D 表示 一位 非数字
+ \s 表示 一位 空白内容(空格 / 制表符)
+ \S 表示 一位 非空白内容
+ \w 表示 一位 数字字母下划线(0-9a-zA-Z_) 任意一个就行
+ \W 表示 一位 非数字字母下划线内容
+ . 表示 非换行 的任意内容
+ \ 转义符
=> 把有意义的符号转换成没有意义的文本
=> 把没有意义的文本转换成有意义的符号
四.边界符
/*
边界符
1. ^ 表示开头
2. $ 表示结尾
注意: 当 ^ 和 $ 一起使用的时候, 表示 从 开头 到 结尾
=> 当你书写正则表达式的时候
=> 如果没有 ^ 和 $ 表示的意义是 "包含"
=> 当你 ^ 和 $ 一起使用的时候, 表示的意义是 "只能"
*/
// 含义: 表示 包含 一位数字
// let reg = /\d/
// 1. ^
// 含义: 表示 以 一位数字 开头
// let reg = /^\d/
// 2. $
// 含义: 表示 以 一位数字 结尾
// let reg = /\d$/
// 含义: 表示从开头到结尾只能有一位数字组成
let reg = /^\d$/
console.log(reg.test('123'))
console.log(reg.test('1a3'))
console.log(reg.test('13'))
console.log(reg.test('3'))
五.限定符
限定符
+ 作用: 限定出现次数使用
+ 注意: 一个限定符只能描述前面一个符号的出现次数
1. * 表示 0 ~ 多次
2. + 表示 1 ~ 多次
3. ? 表示 0 ~ 1 次
4. {n} 表示限定 n 次
5. {n,} 表示限定 n ~ 多次
=> {0,} 等价于 *
=> {1,} 等价于 +
6. {n,m} 表示限定 n ~ m 次
=> {0,1} 等价于 ?
六.特殊符号
特殊符号
1. ()
+ 含义1: 单独捕获(欠着)
+ 含义2: 一个整体
2. |
+ 含义: 或
+ 注意: 或 的边界, 要么是 下一个 |, 要么是 () 边界, 要么是正则边界
3. []
+ 含义: 包含
+ 注意: 一个 [] 必定只占一个字符位置, 表示任意一个都行
4. [^]
+ 含义: 非
+ 注意: 一个 [^] 必定只占一个字符位置, 表示任意一个都不行
5. -
+ 含义: 至 到
+ 一般和 [] 还有 [^] 连用, 表示 unicode 编码连续的
=> [0-9] 等价于 \d
=> [^0-9] 等价于 \D
=> [a-zA-Z0-9_] 等价于 \w
=> [^a-zA-Z0-9_] 等价于 \W
+ 特殊: [\u4e00-\u9fa5] 表示所有中文内容
*/
// 1. ()
// {2} 修饰的是 () 这个整体内容
// let reg = /^(abc){2}$/
// console.log(reg.test('abcabc'))
// console.log(reg.test('abcc'))
// 2. |
// let reg = /^12(a|b)34$/
// console.log(reg.test('12a34'))
// console.log(reg.test('12b34'))
// console.log(reg.test('12c34'))
// console.log(reg.test('1234'))
// 小练习
// let reg = /^(a|b|c)$/
// console.log(reg.test('a'))
// console.log(reg.test('b'))
// console.log(reg.test('c'))
// console.log(reg.test('c1'))
// console.log(reg.test('aa'))
// let reg = /^a|b|c$/
// console.log(reg.test('aaaa'))
// console.log(reg.test('abbb'))
// console.log(reg.test('abbbc'))
// console.log(reg.test('55666b1234'))
// console.log(reg.test('da123123123c'))
// {2} 修饰的是 () 这个整体
// 只要 整体 出现两次即可, 没有要求两次必须一样
// 整体 可以是 'abc' 也可以是 'def'
// let reg = /^(abc|def){2}$/
// console.log(reg.test('abcabc'))
// console.log(reg.test('defdef'))
// console.log(reg.test('abcdef'))
// console.log(reg.test('defabc'))
// 3. []
// 表示该字符串只能由一个字符组成, 这个字符可以是 a 或者 b 或者 c 或者 d
// let reg = /^[abcd]$/
// console.log(reg.test('a'))
// console.log(reg.test('b'))
// console.log(reg.test('c'))
// console.log(reg.test('d'))
// console.log(reg.test('e'))
// console.log(reg.test('aa'))
// let reg = /^[100]$/
// console.log(reg.test('1'))
// console.log(reg.test('0'))
// console.log(reg.test('100'))
// 4. [^]
// 表示该字符串只能由一个字符组成, 只要不是 a 不是 b 不是 c 不是 d 都可以
// let reg = /^[^abcd]$/
// console.log(reg.test('ef'))
// console.log(reg.test('e'))
// console.log(reg.test('f'))
// 5. -
let reg = /^[a-z]$/
console.log(reg.test('a'))
console.log(reg.test('m'))
console.log(reg.test('x'))
七.重复符号
/*
重复符号
+ \1 \2 \3 \4 \5 \6 \7 \8 \9
+ 意义: 表示重复一个第 n 个小括号一模一样的内容
+ 注意: 这个数字不是多少遍, 是第几个小括号
*/
// 表示把 () 内容重复两遍
// let reg = /^(abc|def){2}$/
// 表示在 \1 位置出现一个和 () 位置出现的内容一模一样的才行
// let reg = /^(abc|def)\1$/
// console.log(reg.test('abcabc'))
// console.log(reg.test('defdef'))
// console.log(reg.test('abcdef'))
// console.log(reg.test('defabc'))
// let str = '<span></span>'
// 使用转义符(\) 把有意义的(/) 转换成没有意义的'/' 文本
// let reg = /^<(span|p)>.*<\/\1>$/
// console.log(reg.test(str))
// let reg = /^(ab|cd)(11|22)\2\1$/
// console.log(reg.test('cd1111cd'))
// console.log(reg.test('cd2222cd'))
// console.log(reg.test('ab1111ab'))
// console.log(reg.test('ab2222ab'))
八.标识符
/*
标识符
+ 书写在正则的外面, 用来修饰整个正则表达式使用的
1. i
+ 表示 忽略大小写
2. g
+ 表示 若正则无标识符g 第一次从索引0开始检测
有全局 g 第二次从第一次的结束位置开始向后检索直到找不到
*/
// let reg = /^[abc]$/
// i 表示该正则在检测字符串的时候, 忽略大小写不计
let reg = /^[abc]$/i
console.log(reg.test('a'))
console.log(reg.test('b'))
console.log(reg.test('c'))
console.log(reg.test('A'))
// 全局标识符 g 的作用
let reg = /\d{3}/g
console.log(reg.exec('aaa123bbb456ccc789ddd012eeee'))
console.log(reg.exec('aaa123bbb456ccc789ddd012eeee'))
console.log(reg.exec('aaa123bbb456ccc789ddd012eeee'))
console.log(reg.exec('aaa123bbb456ccc789ddd012eeee'))
console.log(reg.exec('aaa123bbb456ccc789ddd012eeee'))
console.log(reg.exec('aaa123bbb456ccc789ddd012eeee'))
// 小括号的作用
let reg = /(\d{2})(\d{2})(\d{2})(?<year>\d{4})(?<month>\d{2})(?<date>\d{2})\d{2}(\d)(?:\d|x)/
let res = reg.exec('你好, 我是前端小灰狼, 我的身份证号是 11010820050223001x, ^_^')
console.log(res)
九.正则的两大特性
/*
正则的两大特性
1. 懒惰性
+ 每次检索的时候, 都会从 [0] 开始对字符串进行检索
+ 解决: 加一个全局标识符 g
2. 贪婪性
+ 当我们使用限定符的时候, 会尽可能多的去捕获内容
+ 贪婪限定符
=> +
=> *
=> ?
=> {n,}
=> {n,m}
+ 解决: 使用非贪婪限定符
=> +?
=> *?
=> ??
=> {n,}?
=> {n,m}?
=> 当你使用的是非贪婪限定符的时候, 会尽可能少的去捕获内容
*/
十.字符串常用方法(以下方法可以和正则表达式合作)
1. replace()
+ 语法:
=> 字符串.replace(换下字符, 换上字符)
=> 字符串.replace(正则表达式, 换上字符)
+ 返回值: 替换好的字符串
=> 当你使用 字符串片段 或者 没有全局标识符g 的正则表达式的时候, 只能替换一个
=> 当你使用的正则表达式有全局标识符 g 的时候, 有多少替换多少
2. search()
+ 语法:
=> 字符串.search(字符串片段)
=> 字符串.search(正则表达式)
+ 返回值:
=> 如果字符串内有满足要求的字符串片段, 那么返回的就是字符串片段开始的索引位置
=> 如果字符串内没有满足要求的字符串片段, 那么就是 -1
3. match()
+ 语法:
=> 字符串.match(字符串片段)
=> 字符串.match(正则表达式)
+ 返回值:
=> 如果你传递的参数是 字符串片段 或者 没有全局标识符g的正则表达式 那么返回值和 exec 一模一样
=> 如果传递的参数是 带有全局标识符g的正则表达式, 那么返回值是一个数组, 字符串内有多少满足要求的捕获多少
*/
// 1. replace
// let str = 'aaa123bbb123ccc789ddd012eee'
// let r1 = str.replace('123', '**')
// console.log(r1)
// let r2 = str.replace(/\d{3}/, '**')
// console.log(r2)
// let r3 = str.replace(/\d{3}/g, '**')
// console.log(r3)
// let str = 'sdHHuyNNfgMMuyNNksMMadHHfbHHvhNNjk'
// let list = [ 'HH', 'NN', 'MM' ]
// let reg = new RegExp(`(${ list.join('|') })`, 'g')
// let res = str.replace(reg, '**')
// console.log(res)
// 2. search
// let str = '你好 世界 123 hello world'
// let r1 = str.search('123')
// let r2 = str.search(/\d{3}/)
// console.log(r2)
// 3. match
// let str = 'aaa123bbb123ccc789ddd012eee'
// let r1 = str.match('123')
// console.log(r1)
// let r2 = str.match(/\d{3}/)
// console.log(r2)
// let r3 = str.match(/\d{3}/g)
// console.log(r3)
十一.正则的预查(断言)
/*
正则的预查(断言)
+ 了解
+ 两种
=> 正向预查
-> 正向肯定预查 (?=内容)
+ 示例: /windows(?=\d+)/
+ 捕获 windows, 后面紧跟着有 1 ~ 多个数字的 windows
-> 正向否定预查 (?!内容)
+ 示例: /windows(?!\d+)/
+ 捕获 windows, 后面紧跟着不是 1 ~ 多个数字的 windows
=> 负向预查
-> 负向肯定预查 (?<=内容)
+ 示例: /(?<=\d+)windows/
+ 捕获 windows, 前面紧挨着是 1 ~ 多个数字的 windows
-> 负向否定预查 (?<!内容)
+ 示例: /(?<!\d+)windows/
+ 捕获 windows, 前面紧挨着不是 1 ~ 多个数字的 windows
? 表示 贪婪限定符 0 ~ 1 次
?? 表示 非贪婪限定符 0 ~ 1 次
(?:) 表示 匹配但不捕获
(?<变量>) 表示 单独起名小分组
(?=内容) 正向肯定预查
(?!内容) 正向否定预查
(?<=内容) 负向肯定预查
(?<!内容) 负向否定预查
^ 开头
[^] 非
*/
十二.案例-歌词条的滚动
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
audio {
display: block;
margin: 20px auto;
}
.lrc-box {
width: 300px;
height: 200px;
margin: 0 auto;
border: 1px solid #333;
overflow: hidden;
position: relative;
}
.lrc-box > ul {
width: 100%;
position: absolute;
left: 0;
top: 100px;
transition: top .3s linear;
}
.lrc-box > ul > li {
list-style: none;
height: 20px;
line-height: 20px;
text-align: center;
color: rgba(0, 0, 0, .5);
}
.lrc-box > ul > li.active {
background-color: skyblue;
color: rgba(255, 255, 255, 1);
}
</style>
</head>
<body>
<audio src="./data/丑八怪.mp3" controls></audio>
<div class="lrc-box">
<ul>
<li class="active">1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
</div>
<script src="./data/lrc.js"></script>
<script>
/*
案例 - 歌词滚动
+ 准备: 装备一个 lrc 歌词
*/
// 0. 获取元素
const ulBox = document.querySelector('ul')
const audioEle = document.querySelector('audio')
// 0. 准备变量
// 专门用来存储每一句歌词的时间
const timeArr = []
// 准备一个正则表达式
const reg = /\[(?<time>\d{2}:\d{2})\.\d{2}\](?<lrc>.+)/g
// 准备一个变量接受 html 格式字符串
let lrcStr = ''
// 1. 对 lrc 这个 字符串进行分割
// 把里面的 时间 提取出来, 把 歌词 渲染在页面上
// 能从 lrc 内捕获多少内容出来
// 只要捕获一次不是 null, 就需要再次捕获
let res = reg.exec(lrc)
while (res !== null) {
// 当前的 res 捕获到内容了
// console.log(res[1], ' --- ', res[2])
// 把所有的时间放在 timeArr 内存起来
timeArr.push(res[1])
// 把所有的歌词组装成一个 字符串
lrcStr += `<li>${ res[2] }</li>`
res = reg.exec(lrc)
}
// 代码执行到这里, 表示 res 为 null 了, 表示全部捕获完毕
// 2. 把 歌词 插入到 ul 内
ulBox.innerHTML = lrcStr
// 3. 随着音乐的播放, 让 ul 进行 top 值的改变
// 3-1. 给 audio 标签绑定一个 timeupdate 事件
// 随着视音频的播放而触发(1s 3 ~ 4 次)
audioEle.ontimeupdate = function () {
// 3-2. 拿到当前播放的时间
// 语法: 视音频元素.currentTime
// 得到的就是该视音频当前的时间(单位是 s)
const current = Math.round(audioEle.currentTime)
// 3-3. 对拿到的时间 进行 格式化, 格式化成 '00:00'
const minutes = parseInt(current / 60)
const seconds = current % 60
const time = `${ minutes >= 10 ? minutes : '0' + minutes }:${ seconds >= 10 ? seconds : '0' + seconds }`
// 3-4. 去到 timeArr 内查找时间对应的那一个内容
// 因为 timeArr 的 索引, 和 li 的索引是一一对应的
const index = timeArr.findIndex(item => item === time)
if (index === -1) return
// 3-5. 根据 index 的值调整 ul 的定位
// index === 0, [0] 的 li 显示, ul 的 top 值是 100
// index === 1, [1] 的 li 显示, ul 的 top 值是 100 -= 20
// index === 2, [2] 的 li 显示, ul 的 top 值是 100 -= 40
// index === 3, [3] 的 li 显示, ul 的 top 值是 100 -= 60
// index === x, [x] 的 li 显示, ul 的 top 值是 100 -= 20 * x
ulBox.style.top = 100 - index * 20 + 'px'
// 3-5. 只能给 [index] 对应的 li 添加 active 类名
// 如何拿到所有的 li: ul 内的所有子元素
// 拿到一个节点的所有子元素: 节点.children
for (let i = 0; i < ulBox.children.length; i++) {
ulBox.children[i].classList.remove('active')
}
ulBox.children[index].classList.add('active')
}
</script>
补充:
3-1. 给 audio 标签绑定一个 timeupdate 事件
随着视音频的播放而触发(1s 3 ~ 4 次)
audioEle.ontimeupdate = function () {
3-2. 拿到当前播放的时间
语法: 视音频元素.currentTime
得到的就是该视音频当前的时间(单位是 s)
const current = Math.round(audioEle.currentTime)
先清除所有 再加上
for (let i = 0; i < ulBox.children.length; i++) {
ulBox.children[i].classList.remove('active')
}
ulBox.children[index].classList.add('active')