一、事件对象e(event)
- 所有的标签添加点击事件之后,这个事件的执行函数,会默认接收一个参数我们一殷会将这个参数命名为
event
;但是名字有点麻烦,所以有人会写ev
;现在更加习惯写e
; - 这个形参
e
是一个对象,里面有很多属性,其中一个叫target
; - 这个属性的值就是你点击的标签;
js
box.onclick = function(e) {
console.log(e.target)//打印出的内容是-你点击的标签
}
二、扩展
1、反引号
- 字符串需要使用
''
或者""
包裹,但是不能换行,内部也不能书写变量;如果想要书写换行或者书写变量,那么需要使用反引号(``) - 如果需要在反引号内书写换行,那么直接换行就行
- 如果需要在反引号内书写变量,需要使用
${}
将变量包裹起来
js
return `
<li>
<img src="${item.imgurl}"
alt="">
<p>${item.title}</p>
</li>
`
2、Object
Object.keys()
获取对象的全部属性名组成的数组Object.values()
获取对象的全部属性值组成的数组-
3、事件冒泡
- 当你点击了一个元素的时候,相当于触发了这个元素的点击事件;当一个元素的点击事件被触发的时候,那么按照js中事件冒泡的特性会将这个事件的触发也传递给自己的父级
- 冒泡会从点击的元素开始, 一直到页面最顶层的元素;哪怕你的元素没有绑定点击事件, 也会触发事件冒泡
js
<style>
.box1 {
width: 500px;
height: 500px;
background-color: skyblue;
}
.box2 {
width: 300px;
height: 300px;
background-color: pink;
}
.box3 {
width: 100px;
height: 100px;
background-color: green;
}
.box4 {
width: 60px;
height: 60px;
background-color: rgb(226, 82, 21);
}
</style>
<div class="box1">
<div class="box2">
<div class="box3">
<div class="box4"></div>
</div>
</div>
</div>
js
var box1 = document.querySelector('.box1')
var box2 = document.querySelector('.box2')
var box3 = document.querySelector('.box3')
var myBody = document.querySelector('body')
// box1.onclick = function () {
// console.log('点击了蓝色盒子')
// }
box2.onclick = function () {
console.log('点击了粉色盒子')
}
myBody.onclick = function () {
console.log('body')
}
box3.onclick = function () {
console.log('点击了绿色盒子')
}
4、事件委托
- 件委托就是利用事件冒泡的原理:将所有子元素的一个事件(点击事件), 委托给共同的父级
js
<style>
.box1 {
width: 500px;
height: 500px;
background-color: skyblue;
}
.box2 {
width: 300px;
height: 300px;
background-color: pink;
}
.box3 {
width: 100px;
height: 100px;
background-color: green;
}
.box4 {
width: 60px;
height: 60px;
background-color: rgb(226, 82, 21);
}
ul {
width: 500px;
background-color: rgb(43, 238, 13);
}
li {
width: 100px;
background-color: salmon;
margin-top: 10px;
}
</style>
<!-- 事件冒泡对应代码 -->
<!-- <div class="box1">
<div class="box2">
<div class="box3">
<div class="box4"></div>
</div>
</div>
<div class="box2000">
<div class="box3000">
<div class="box4000"></div>
</div>
</div>
</div> -->
<ul>
<li class="item">1</li>
<li class="item">2</li>
<li class="item">3</li>
<li class="item">4</li>
</ul>
js
// 1. 绑定事件
// var lis = document.querySelectorAll('li')
// for (var i = 0; i < lis.length; i++) {
// lis[i].onclick = function () {
// console.log('事件触发')
// }
// }
// 2. 新增一个 li
// var myLi = document.createElement('li')
// myLi.innerHTML = '我是通过 JS 创建的, 请测试我是否具有点击事件'
// document.querySelector('ul').appendChild(myLi)
/**
* 上述代码中 因为是 先获取的 li , 然后绑定完事件之后, 又新增了li
*
* 所以新增的 li 就没有事件
*
* 所以解决上述问题的最好的方案就是 利用 事件委托
* 将 所有 li 的事件 委托给 共同的 父级
*/
var myUl = document.querySelector('ul')
myUl.onclick = function (e) {
if (e.target.className === 'item') {
console.log('触发事件')
}
}
// 2. 新增一个 li
var myLi = document.createElement('li')
myLi.className = 'item'
myLi.innerHTML = '我是通过 JS 创建的, 请测试我是否具有点击事件'
document.querySelector('ul').appendChild(myLi)
三、元素的偏移量
1、获取元素偏移量
- 就是元素在页面上相对于参考父级的左边和上边的距离
1)offsetParent
- 获取元素的偏移量参考父级
- 其实就是假设你要给一个元素 绝对定位 的时候
- 它是根据谁来进行定位的, 那么这个元素的偏移量参考父级就是谁
js
//获取元素的参考父级
console.log(box_s.offsetParent)//div.box
console.log(box.offsetParent)//body
2)offsetLeft / offsetTop
- 获取的事元素左边的偏移量和上边的偏移量
- offsetLeft 该元素相对于参考父级的左侧偏移量
- offsetTop 该元素相对于参考父级的上侧偏移量
js
var box = document.querySelector('.box')
var box_s = document.querySelector('.box_s')
//获取元素的偏移量 获取到的是一个数字,没有px单位
console.log('left: ', box_s.offsetLeft)//32
console.log('top: ', box_s.offsetTop)//31
console.log(box_s.offsetTop + 'px')//31px
2、获取元素尺寸
- 获取元素的占地面积
1)offsetWidth 和 offsetHeight
- offsetWidth: 获取元素内容 + padding + border 的宽度
- offsetHeight: 获取元素内容 + padding + border 的高度
js
var div1 = document.querySelector('.div1')
console.log('offsetWidth',div1.offsetWidth)//360
console.log('offsetHeight',div1.offsetHeight)//360
2)clientWidth 和 clientHeight
- clientWidth 获取元素内容 + padding 的宽度
- clientHeight 获取元素内容 + padding 的高度
js
var div2 = document.querySelector('.div2')
console.log('offsetWidth',div2.clientWidth)//340
console.log('offsetHeight',div2.clientHeight)//340
注意
- 获取到的尺寸是没有单位的数字
- 当元素在页面中不占位置的时候, 获取到的是 0
display: none
元素在页面不占位visibility: hidden
元素在页面占位
3、获取浏览器窗口尺寸
-
可视区域的尺寸
-
document.documentElement.clientWidth
: 浏览器可视窗口的宽度 -
document.documentElement.clientHeight
: 浏览器可视窗口的高度
四、js分页案例
1、思路
markdown
分页的功能
1. 刚打开页面
1.1 截取部分数据, 渲染页面
1.2 调整页码
1.3 调整上下页按钮的样式
2. 点击上一页
2.1 判断能不能去上一页
2.2 截取部分数据, 渲染页面
2.3 调整页码
2.4 调整上下页按钮的样式
3. 点击下一页
3.1 判断能不能去下一页
3.2 截取部分数据, 渲染页面
3.3 调整页码
3.4 调整上下页按钮的样式
4. 切换每页展示数据的数量
4.1 调整每页展示的数量
4.2 截取部分数据, 渲染页面
4.3 调整页码
4.4 调整上下页按钮的样式
逻辑:
0. 创建一个渲染函数
0.1 截取部分数据
0.2 调整页码
0.3 调整按钮样式
1. 初次打开页面
1.1 直接调用
2. 点击上一页按钮
2.1 判断能不能去上一页
2.2 调整当前页
2.3 调用渲染函数
3. 点击下一页按钮
3.1 判断能不能去下一页
3.2 调整当前页
3.3 调用渲染函数
4. 切换每页展示数量
4.1 切换展示的数量
4.2 调用渲染函数
2、实现
js
// 0. 获取标签
var total = document.querySelector('.total')
var prev = document.querySelector('.prev')
var next = document.querySelector('.next')
var select = document.querySelector('select')
// 0. 创建全局变量 (在当前 JS 中, 任何一个地方都能使用)
var currentNum = 1 // 默认在 第一页
var pageSize = 4 // 默认每页展示 4 条数据
var totalNum = 0 // 计算总页码
// 0. 创建一个渲染函数
function bindHtml() {
/**
* 0.1 截取部分数据, 渲染页面
*
* 假设 当前是第 1 页 每页展示 4 条
* 页码 === 1 => [0]~[3]
* 页码 === 2 => [4]~[7]
* 页码 === 3 => [8]~[11]
*
* 我们假设 当前页的数字存储在 currentNum 中, 每页展示多少条的数字存储在 pageSize 中
*
* 第一版
* 开始下标: (currentNum - 1) * pageSize
* 结束下标: currentNum * pageSize - 1
*
* 但是 我们用的截取的方法参数有一个特点: 包前不包后, 所以开始下标不变, 结束下标 需要 + 1
*
* 第二版
* 开始下标: (currentNum - 1) * pageSize
* 结束下标: currentNum * pageSize - 1 + 1
*
* 所以最终的优化版本
*
* 第三版
* 开始下标: (currentNum - 1) * pageSize
* 结束下标: currentNum * pageSize
*/
var newArr = list.slice((currentNum - 1) * pageSize, currentNum * pageSize)
var htmlStr = ""
for (var i = 0; i < newArr.length; i++) {
htmlStr += `
<li>
<img src="${newArr[i].pic}" alt="">
<p>${newArr[i].name}</p>
<p>城市: ${newArr[i].city}</p>
<p>地址: ${newArr[i].address}</p>
<p>价格: ${newArr[i].price}</p>
<p>时间: ${newArr[i].showTime}</p>
</li>
`
}
document.querySelector('ul').innerHTML = htmlStr
// 0.2 调整页码
totalNum = Math.ceil(list.length / pageSize)
total.innerHTML = currentNum + ' / ' + totalNum
// 0.3 调整按钮样式
prev.className = currentNum === 1 ? 'prev disable' : 'prev'
next.className = currentNum === totalNum ? 'next disable' : 'next'
}
// 1. 初次打开页面 直接调用
bindHtml()
// 2. 点击上一页
prev.onclick = function () {
// 2.1 判断能不能去上一页
if (currentNum === 1) return
// 2.2 调整页码
currentNum--
// 2.3 重新渲染
bindHtml()
}
// 3. 点击下一页
next.onclick = function () {
// 3.1 判断能不能去下一页
if (currentNum === totalNum) return
// 3.2 调整页码
currentNum++
// 3.3 重新渲染
bindHtml()
}
// 4. 切换每页展示多少条
select.onchange = function () {
// console.log('选择框的内容改变了', select.value - 0)
currentNum = 1
// 修改每页展示多少条
pageSize = select.value - 0
// 重新渲染页面
bindHtml()
}
五、js瀑布流案例
1、思路
瀑布流: 是目前主流的一个前端分页方式
markdown
1. 打开页面渲染一套数据
2. 当页面滚动到某一个位置的时候, 重新请求新的数据
逻辑:
封装一个渲染函数
首次打开页面的时候调用
当页面滚动到某一个位置的时候, 重新调用渲染函数, 拼接上新的数据
我们规定 每页固定展示 8 条数据, 因为要首次渲染的时候, 撑满首页
2、实现
js
var myUl = document.querySelector('ul')
var loadding = document.querySelector('.loadding')
// 0. 全局变量
var currentNum = 1 // 表示当前页
var pageSize = 8 // 表示每页展示多少条 (这个变量不会被更改)
var totalNum = Math.ceil(list.length / pageSize) // 计算总页码
var flag = true // 作为一个开关变量, 用于控制是否请求新数据
// 0. 封装渲染函数
function bindHtml() {
var newArr = list.slice((currentNum - 1) * pageSize, currentNum * pageSize)
var htmlStr = ""
for (var i = 0; i < newArr.length; i++) {
htmlStr += `
<li>
<img src="${newArr[i].pic}" alt="">
<p>${newArr[i].name}</p>
<p>城市: ${newArr[i].city}</p>
<p>地址: ${newArr[i].address}</p>
<p>价格: ${newArr[i].price}</p>
<p>时间: ${newArr[i].showTime}</p>
</li>
`
}
// 因为瀑布流需要的是拼接数据, 所以此处不应该使用 = , 而是使用 +=
myUl.innerHTML += htmlStr
}
// 1. 首次打开页面的时候调用
bindHtml()
// 2. 给页面添加滚动事件
window.onscroll = function () {
// 2.1 如果当前页 === 总页码 代表没有下一页了, 所以这个事件可以不执行了
if (currentNum === totalNum) return
/**
* 2.2 如果代码能够执行到这个位置, 说明还有数据
*
* 但是需要在 UL 的底边到达 页面的可视区域的时候, 在加载新数据
*
*
* 页面卷去的高度 + 浏览器可视区域的高度 > UL 顶部偏移量 + UL 的高度 满足这个条件 加载新数据
*
* 页面卷去的高度 + 浏览器可视区域的高度 <= UL 顶部偏移量 + UL 的高度 满足这个条件 不需要加载新数据 直接 return
*/
// 页面卷去的高度
var docHeight = document.documentElement.scrollTop
// 浏览器可视区域的高度
var winHeight = document.documentElement.clientHeight
// UL 顶部偏移量
var ULTop = myUl.offsetTop
// UL 的高度
var ULHeight = myUl.offsetHeight
if (docHeight + winHeight < ULTop + ULHeight) return
// 2.3 如果代码执行到这里, 说明后续还有数据, 并且到了加载新数据的时候了
// 2.3.1 查看开关变量, 是否允许我们请求
if (!flag) return
// 2.3.2 开始请求数据前, 关闭开关变量, 直到这一次请求完毕的时候, 才能重新发起请求
flag = false
// 数据请求前 打开 loadding
loadding.style.display = 'flex'
console.log('此时请求了 1 次新数据')
setTimeout(function () {
currentNum++
bindHtml()
loadding.style.display = 'none'
// 2.3.3 此时这一次请求已经完毕了, 可以打开开关, 允许我们发送第二次请求, 加载新的数据
flag = true
}, 2000)
}
六、渲染表格
js
<script>
var data = JSON.parse(window.localStorage.getItem('data')) || [
{
status: true,
id: 1,
name: '张三',
age: 25,
city: '北京',
},
{
status: false,
id: 2,
name: '李四',
age: 30,
city: '上海',
},
{
status: true,
id: 3,
name: '王五',
age: 22,
city: '杭州',
}
]
// 页面打开时调用渲染函数
bindHtml()
// 创建一个渲染函数
function bindHtml() {
// 在页面渲染前, 先清空之前的页面
document.querySelector('table').innerHTML = ""
// 在页面渲染前, 先计算出有多少个数据被选中, 用于决定是否选中全选
var checkedNum = 0 // 存储当前数据中 有多少个数据被选中了
for (var q = 0; q < data.length; q++) {
data[q].status && checkedNum++ // 如果当前对象的status属性为 true, 那么代表当前对象被选中了, 所以计数器 + 1
}
// ==================创建表头内容开始==================
var headerArr = ['选择', '编号', '姓名', '年龄', '城市']
var headerTr = document.createElement('tr') // 当前tr只需要创建一个, 内部放一些 th 也就是 表头
for (var j = 0; j < headerArr.length; j++) { // 根据数据批量创建表头
var headerTh = document.createElement('th') // 创建一个表头
if (j === 0) { // 如果 j === 0, 代表循环第一次执行, 那么这个单元格的内容, 应该是一个多选框
var inp = document.createElement('input') // 创建一个 input 标签, 注意: 默认为 输入框
inp.type = 'checkbox' // 如果没有这一行那么创建的默认是一个 单行文本输入框
inp.className = 'check_all' // 因为一会需要添加事件, 所以这里提前给这个选择框添加一个类名标识
inp.checked = checkedNum === data.length // 根据 现有选中的数据数量和总数据数量对比, 如果相同代表所有数据被选中, 那么选中全选按钮
headerTh.appendChild(inp) // 将 多选框 添加到 th 中
} else {
headerTh.innerHTML = headerArr[j] // 给表头单元格赋值一个文本
}
headerTr.appendChild(headerTh) // 将 th 单元格 放到 tr 单元行 内
}
document.querySelector('table').appendChild(headerTr)
// ==================创建表头内容结束==================
// ==================创建表格内容开始==================
for (var i = 0; i < data.length; i++) {
var myTr = document.createElement('tr')
var dataKeys = Object.keys(data[i])
for (var k = 0; k < dataKeys.length; k++) {
var myTd = document.createElement('td')
// 如果 展示的内容是一个 布尔值, 那么应该展示一个多选框
if (data[i][dataKeys[k]] === true || data[i][dataKeys[k]] === false) {
var inpItems = document.createElement('input') // 创建一个 input 标签, 注意: 默认为单行输入框
inpItems.type = 'checkbox' // 更改类型为 多选框
inpItems.className = 'check_item' // 给多选框添加类名, 因为一会需要添加事件
inpItems.checked = data[i][dataKeys[k]] // 给多选框 添加一个默认的选中状态, 需要根据数据提供的来展示
inpItems.dataset.id = i + 1 // 用于给 标签 添加一个 标识, 能够知道将来点的是那个
myTd.appendChild(inpItems)
// 将 多选框 添加到 td 单元格中
} else {
myTd.innerText = data[i][dataKeys[k]] // 如果 展示的内容不是 布尔值, 那么正常展示文本
}
myTr.appendChild(myTd)
}
document.querySelector('table').appendChild(myTr)
}
// ==================创建表格内容结束==================
// ==================持久化数据==================
window.localStorage.setItem('data', JSON.stringify(data))
}
// ==================全选功能 (事件委托) 开始==================
var myTable = document.querySelector('table')
myTable.onclick = function (e) {
if (e.target.className === 'check_all') {
// 1. 修改数据
for (var i = 0; i < data.length; i++) {
data[i].status = e.target.checked
}
// 2. 重新渲染页面
bindHtml()
}
if (e.target.className === 'check_item') {
// 1. 修改数据
for (var k = 0; k < data.length; k++) {
if (e.target.dataset.id - 0 === data[k].id) {
data[k].status = e.target.checked
}
}
// 2. 重新渲染页面
bindHtml()
}
}
// ==================全选功能 (事件委托) 结束==================
// ==================排序功能开始==================
var sortBtn = document.querySelector('#sort_btn')
var num = 0
sortBtn.onclick = function () {
num++ // 每次点击的时候 修改计数器的数字
if (num === 1) { // 1. 第一次点击 按照 年龄的从小到大
data.sort(function (a, b) { return a.age - b.age })
} else if (num === 2) { // 2. 第二次点击 按照 年龄的从大到小
data.sort(function (a, b) { return b.age - a.age })
} else { // 3. 第三次点击 恢复默认排序(按照ID从小到大)
data.sort(function (a, b) { return a.id - b.id })
num = 0 // 清零计数器, 下次可以重新执行逻辑
}
// 上述的 if 语句内 处理完数据后, 重新渲染页面
bindHtml()
}
// ==================排序功能结束==================
// ==================新增功能开始==================
// 1. 点击 "新增按钮" 打开 遮罩层 和 信息框
var addBtn = document.querySelector('#add_btn')
var overlay = document.querySelector('.overlay')
var addUserBox = document.querySelector('.add_user_box')
addBtn.onclick = function () {
overlay.classList.remove('close')
addUserBox.classList.remove('close')
}
// 2. 点击 "新增用户按钮" 收集用户输入的信息, 然后创建一个对象, 追加到原数组的末尾, 最后重新渲染页面
var addUserBtn = document.querySelector('.add_user_btn')
addUserBtn.onclick = function () {
// 2.1 获取到用户输入的信息
var nameEl = document.querySelector('.username')
var ageEl = document.querySelector('.userage')
var cityEl = document.querySelector('.usercity')
var nameValue = nameEl.value
var ageValue = ageEl.value
var cityValue = cityEl.value
// 2.2 安全校验 (非空校验, 规则校验/正则校验)
if (nameValue === '' || ageValue === '' || cityValue === '') return alert('您的输入框有一个为空, 请补全输入框')
// 2.3 如果代码能够正常执行到这里, 说明输入框的的内容不是空的, 所以可以组装一个对象, 然后追加到原数组的末尾
var obj = {
status: false,
id: data.length + 1,
name: nameValue,
age: ageValue - 0,
city: cityValue,
}
data.push(obj)
// 2.4 调用渲染函数
bindHtml()
// 2.5 清空弹框数据
nameEl.value = ''
ageEl.value = ''
cityEl.value = ''
// 2.6 关闭弹框
closeBoxFn()
}
// 3. 点击 "取消按钮" 关闭 遮罩层和信息框
var closeBoxBtn = document.querySelector('.close_box_btn')
closeBoxBtn.onclick = closeBoxFn
function closeBoxFn() {
overlay.classList.add('close')
addUserBox.classList.add('close')
}
// ==================新增功能结束==================
// ==================获取平均年龄开始==================
var getAge = document.querySelector('#get_age')
getAge.onclick = function () {
// 1. 获取到年龄的总和
var sum = 0
for (var i = 0; i < data.length; i++) {
sum += data[i].age
}
// 2. 总和 / 总数量 === 年龄的平均值
sum = sum / data.length
// 3. 提示给用户年龄的平均值
alert(sum.toFixed(1))
}
// ==================获取平均年龄结束==================
</script>