一、事件对象e(event)
- 所有的标签添加点击事件之后,这个事件的执行函数,会默认接收一个参数我们一殷会将这个参数命名为
event;但是名字有点麻烦,所以有人会写ev;现在更加习惯写e; - 这个形参
e是一个对象,里面有很多属性,其中一个叫target; - 这个属性的值就是你点击的标签;
box.onclick = function(e) {
console.log(e.target)//打印出的内容是-你点击的标签
}
二、扩展
1、反引号
- 字符串需要使用
''或者""包裹,但是不能换行,内部也不能书写变量;如果想要书写换行或者书写变量,那么需要使用反引号(``) - 如果需要在反引号内书写换行,那么可以直接换行
- 如果需要在反引号内书写变量,需要使用
${}将变量包裹起来
return `
<li>
<img src="${item.imgurl}" alt="">
<p>${item.title}</p>
</li>
`
2、Object
Object.keys()获取对象的全部属性名组成的数组Object.values()获取对象的全部属性值组成的数组
3、事件冒泡
- 当你点击了一个元素的时候,相当于触发了这个元素的点击事件;当一个元素的点击事件被触发的时候,那么按照js中事件冒泡的特性会将这个事件的触发也传递给自己的父级
- 冒泡会从点击的元素开始, 一直到页面最顶层的元素;哪怕你的元素没有绑定点击事件, 也会触发事件冒泡
// CSS代码
<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>
// html
<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、事件委托
- 件委托就是利用事件冒泡的原理:将所有子元素的一个事件(点击事件), 委托给共同的父级
<style>
.box1 {
width: 500px;
height: 500px;
background-color: skyblue;
}
.box2 {
width: 300px;
height: 300px;
background-color: pink;
}
.box3 {
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
- 获取元素的偏移量参考父级
- 其实就是假设你要给一个元素 绝对定位 的时候
- 它是根据谁来进行定位的, 那么这个元素的偏移量参考父级就是谁
//获取元素的参考父级
console.log(box_s.offsetParent)//div.box
console.log(box.offsetParent)//body
2)offsetLeft / offsetTop
-
获取的事元素左边的偏移量和上边的偏移量
- offsetLeft 该元素相对于参考父级的左侧偏移量
- offsetTop 该元素相对于参考父级的上侧偏移量
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 的高度
var div1 = document.querySelector('.div1')
console.log('offsetWidth',div1.offsetWidth) // 360
console.log('offsetHeight',div1.offsetHeight) // 360
2)clientWidth 和 clientHeight
- clientWidth 获取元素内容 + padding 的宽度
- clientHeight 获取元素内容 + padding 的高度
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、思路
/* 分页的功能
* 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、实现
// 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、思路
瀑布流: 是目前主流的一个前端分页方式
1. 打开页面渲染一套数据
2. 当页面滚动到某一个位置的时候, 重新请求新的数据
逻辑:
封装一个渲染函数
首次打开页面的时候调用
当页面滚动到某一个位置的时候, 重新调用渲染函数, 拼接上新的数据
我们规定 每页固定展示 8 条数据, 因为要首次渲染的时候, 撑满首页
2、实现
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
}
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)
}
六、渲染表格
<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++) {
// 如果当前对象的status属性为 true, 那么代表当前对象被选中了, 所以计数器 + 1
data[q].status && checkedNum++
}
// =========创建表头内容开始=========
var headerArr = ['选择', '编号', '姓名', '年龄', '城市']
// 当前tr只需要创建一个, 内部放一些 th 也就是 表头
var headerTr = document.createElement('tr')
// 根据数据批量创建表头
for (var j = 0; j < headerArr.length; j++) {
// 创建一个表头
var headerTh = document.createElement('th')
// 如果 j === 0, 代表循环第一次执行, 那么这个单元格的内容, 应该是一个多选框
if (j === 0) {
// 创建一个 input 标签, 注意: 默认为 输入框
var inp = document.createElement('input')
// 如果没有这一行那么创建的默认是一个 单行文本输入框
inp.type = 'checkbox'
// 因为一会需要添加事件, 所以这里提前给这个选择框添加一个类名标识
inp.className = 'check_all'
// 根据 现有选中的数据数量和总数据数量对比, 如果相同代表所有数据被选中, 那么选中全选按钮
inp.checked = checkedNum === data.length
// 将 多选框 添加到 th 中
headerTh.appendChild(inp)
} else {
// 给表头单元格赋值一个文本
headerTh.innerHTML = headerArr[j]
}
// 将 th 单元格 放到 tr 单元行 内
headerTr.appendChild(headerTh)
}
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) {
// 创建一个 input 标签, 注意: 默认为单行输入框
var inpItems = document.createElement('input')
// 更改类型为 多选框
inpItems.type = 'checkbox'
// 给多选框添加类名, 因为一会需要添加事件
inpItems.className = 'check_item'
// 给多选框 添加一个默认的选中状态, 需要根据数据提供的来展示
inpItems.checked = data[i][dataKeys[k]]
// 用于给 标签 添加一个 标识, 能够知道将来点的是哪个
inpItems.dataset.id = i + 1
// 将 多选框 添加到 td 单元格中
myTd.appendChild(inpItems)
} 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>