事件
事件的组成
- 触发谁的事件: 事件源
- 触发什么事件: 事件类型
- 触发后做什么: 事件处理函数
事件的绑定方式
onXXX
- 这个写法中 on 是一个固定开头, 真正的事件类型, 应该是 (click/change)
- 同一个元素, 只能通过这个方式绑定一个事件,如果写了第二个, 那么第一个会被覆盖
事件监听
addEventListener (非 IE7 8 下使用)
语法: 元素.addEventListener('事件类型', 事件处理函数, 冒泡还是捕获)
oDiv.addEventListener(
"click",
function () {
console.log("我是第一个事件");
},
false
);
oDiv.addEventListener(
"click",
function () {
console.log("我是第二个事件");
},
false
);
- 点击 div 时, 两个函数都会执行, 并且会按照你注册的顺序执行
- 先打印
我是第一个事件, 然后打印我是第二个事件 - 注意: 事件类型需要要写 on, 点击事件就是 onclick
attachEvent (IE 7 8 下 使用)
语法: 元素.attachEvent('事件类型', 事件处理函数)
/**
* 给 header 内的 三个 li 添加点击事件
*
* 当点击到 header 内的 li 的时候, 将 content 内对应顺序的 li 展示
*/
// var lis = document.querySelectorAll('li') // 错误写法: 会获取到页面所有的 li
// console.log(lis)
var lis = document.querySelectorAll('.header li')
var lis_c = document.querySelectorAll('.content li')
// 这个循环只是为了给 每一个 header 内的 li 添加一个 事件, 其他没有任何用处
for (let i = 0; i < lis.length; i++) {
lis[i].addEventListener('click', function () {
// 1. 取消 header 和 content 内 所有 li 的类名
for (var k = 0; k < lis.length; k++) {
lis[k].className = ""
lis_c[k].className = ""
}
// 2. 给点击的 header 内的 li 添加类名
lis[i].className = "active"
// 3. 给对应顺序的 content 内的 li 添加类名
lis_c[i].className = "active"
})
}
常见的事件
浏览器事件
- load: 页面全部资源加载完毕
- scroll: 浏览器滚动的时候触发
鼠标事件
- click: 点击事件 (移动端也能触发点击事件)
- dblclick: 双击事件
- contextmenu: 右键事件
- mousedown: 鼠标左键按下事件
- mouseup: 鼠标左键抬起事件
- mousemove: 鼠标移动
- 移入子元素的时候也会触发事件
- mouseover: 鼠标移入事件
- mouseout: 鼠标移出事件
- 移入子元素的时候 不会触发事件 (推荐使用)
- mouseenter: 鼠标移入事件
- mouseleave: 鼠标移出事件
键盘事件
目前 JS 中 只有 input 输入框和 document 对象可以绑定输入事件
- keyup: 键盘抬起事件
- keydown: 键盘按下事件
- keypress: 键盘按下在抬起事件
表单事件
- focus: 输入框获取焦点
- blur: 输入框失去焦点
- change: 表单内容改变事件
- input: 表单内容输入事件
触摸事件
- touchstart: 触摸开始事件
- touchend: 触摸结束事件
- touchmove: 触摸移动事件
事件对象
- 在每一个事件被触发的时候, 会有一个对象内部存放一些关于当前 事件的一些描述信息
- 浏览器将这个事件信息对象放在了一个叫做 window.event 的属性中
- 在高版本浏览器中书写的方式为 事件处理函数的第一个形参
- 写法最初就是 event 后来简化为 ev 目前简化为 e
// 曾经在 低版本 IE 还有市场使用需求的时候, 我们一般需要一个兼容写法
box.onclick = function (e) {
e = e || window.event
}
点击事件的光标点获取
相对于事件源
- offsetX 和 offsetY
- 相对于我们点击的元素的边框内测开始计算
相对于浏览器窗口你点击的坐标点
- clientX 和 clientY
- 相对于浏览器窗口来计算的, 不管你页面滚动到什么情况, 都是根据窗口来计算坐标
相对于页面你点击的坐标点
- pageX 和 pageY
- 是相对于整个页面的坐标点, 不管有没有滚动, 都是相对于页面拿到的坐标点
获取键盘按键
- e.key(具体按键)
- e.keyCode(Ascll码表)
拖拽案例
<div class="box"></div>
<script>
var box = document.querySelector('.box')
var flag = false // 创建一个开关变量控制移动事件能否执行, 默认为关闭状态, 也就说正常不允许执行
// 保存鼠标按下时的 定位
var startX = 0
var startY = 0
// 保存鼠标按下时的 位置(偏移量)
var startLeft = 0
var startTop = 0
box.onmousedown = function (e) {
flag = true // 当鼠标按下的时候, 打开开关, 允许移动事件触发
// console.log('鼠标按下', e.clientX, e.clientY)
// 按下时 保存 鼠标的定位
startX = e.clientX
startY = e.clientY
// 按下时 保存 元素的偏移量
startLeft = box.offsetLeft
startTop = e.target.offsetTop
}
document.onmousemove = function (e) {
if (!flag) return // 事件函数内部, 先判断 开关是打开还是关闭, 根据开关的状态决定能否执行移动事件
// console.log('鼠标移动前的定位', startX, startY)
// console.log('鼠标移动后的定位', e.clientX, e.clientY)
// 3.1 获取最新的鼠标定位 - 鼠标按下时的定位 === 移动距离
var moveX = e.clientX - startX
var moveY = e.clientY - startY
// 4.1 元素的初始位置 + 移动的距离 === 移动到哪里
var left = startLeft + moveX
var top = startTop + moveY
// 5. 添加一个边界限制
if (left <= 0) left = 0
if (top <= 0) top = 0
if (left > document.documentElement.clientWidth - box.offsetWidth) left = document.documentElement.clientWidth - box.offsetWidth
if (top > document.documentElement.clientHeight - box.offsetHeight) top = document.documentElement.clientHeight - box.offsetHeight
box.style.left = left + 'px'
box.style.top = top + 'px'
}
box.onmouseup = function () {
flag = false // 当鼠标抬起的时候, 关闭开关, 禁止移动事件触发
console.log('鼠标抬起')
}
</script>
购物车案例
/**
* 功能:
* 1. 页面的渲染
* 2. 全选功能
* 3. 总件数和总价格
*
*
* 逻辑:
* 1. 准备一个渲染函数
* * 根据数据渲染商品
* * 根据选中的商品 渲染总件数与总价格 全选按钮
*
* 2. 给每一个商品对应的选中按钮添加事件
* 3. 给全选按钮 添加事件
*/
var cartList = [
{
id: 111234,
status: true,
pic: "https://img1.baidu.com/it/u=2511310783,721605137&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=332",
name: "我是一个手机, 不知道是啥",
price: 100,
number: 3,
total: 16,
},
{
id: 123456,
status: false,
pic: "https://img1.baidu.com/it/u=1537709578,2453227648&fm=253&fmt=auto&app=120&f=JPEG?w=809&h=500",
name: "我是一个电脑, 不知道是啥",
price: 98.72,
number: 1,
total: 7,
},
{
id: 965874,
status: true,
pic: "https://img2.baidu.com/it/u=3561506717,735421650&fm=253&fmt=auto&app=138&f=JPEG?w=750&h=500",
name: "我是一个手纸, 不知道是啥",
price: 356.21,
number: 2,
total: 22,
},
];
// 0. 获取标签
var content = document.querySelector(".content");
// 0. 全局变量
// 0. 创建一个渲染函数
function bindHtml() {
// 计算选中的商品, 总商品数量, 总价格
var checked_num = 0; // 存储选中商品的数量
var checked_num_total = 0; // 存储选中商品所有的收藏数量
var checked_total_price = 0; // 存储选中商品的总价
for (var k = 0; k < cartList.length; k++) {
if (cartList[k].status) {
checked_num++;
checked_num_total = checked_num_total + cartList[k].number;
checked_total_price =
checked_total_price + cartList[k].number * cartList[k].price;
}
}
var str = `
<div class="top">
<input class="checked_all" type="checkbox" ${
checked_num === cartList.length ? "checked" : ""
}> 全选
</div>
<ul>`;
// 利用 循环 遍历 数组, 拿到每一个商品数据, 有多少个商品 就 拼接 多少个 li
for (var i = 0; i < cartList.length; i++) {
str += `
<li>
<div class="status">
<input class="item" data-id="${
cartList[i].id
}" type="checkbox" ${cartList[i].status ? "checked" : ""}>
</div>
<div class="show">
<img src="${cartList[i].pic}" alt="">
</div>
<div class="title">
${cartList[i].name}
</div>
<div class="price">
¥ ${cartList[i].price.toFixed(2)}
</div>
<div class="number">
<button class="prev" data-id="${cartList[i].id}">-</button>
<input type="text" value="${cartList[i].number}">
<button class="next" data-id="${cartList[i].id}">+</button>
</div>
<div class="sub">
¥ ${(cartList[i].number * cartList[i].price).toFixed(2)}
</div>
<div class="destory">
<button class="del_item" data-id="${
cartList[i].id
}">删除</button>
</div>
</li>`;
}
str += `</ul>
<div class="bottom">
<div class="totalNum">
总件数 : ${checked_num_total}
</div>
<div class="btns">
<button class="clear">清空购物车</button>
<button class="pay" data-num="${checked_num}" data-price="${checked_total_price.toFixed(2)}">去结算</button>
<button class="del" data-num="${checked_num}">删除所有已选中</button>
</div>
<div class="totalPrice">
总价格 : ¥ <span>${checked_total_price.toFixed(2)}</span>
</div>
</div>
`;
content.innerHTML = str;
}
// 1. 初次打开页面 调用渲染函数
bindHtml();
// 2. 利用事件委托将后续需要添加的事件 委托给共同的父级 content
content.onclick = function (e) {
// 2.1 全选按钮
if (e.target.className === "checked_all") {
// 1. 更改数据
for (var i = 0; i < cartList.length; i++) {
cartList[i].status = e.target.checked;
}
// 2. 重新渲染页面
bindHtml();
}
// 2.2 更改商品的选中状态
if (e.target.className === "item") {
// 1. 更改数据
for (var k = 0; k < cartList.length; k++) {
if (e.target.dataset.id - 0 === cartList[k].id) {
cartList[k].status = !cartList[k].status;
}
}
// 2. 重新渲染页面
bindHtml();
}
// 增加
if (e.target.className === "prev") {
for (var q = 0; q < cartList.length; q++) {
if (e.target.dataset.id - 0 === cartList[q].id) {
if (cartList[q].number === 1) return;
cartList[q].number--;
}
}
bindHtml();
}
// 减少
if (e.target.className === "next") {
for (var w = 0; w < cartList.length; w++) {
if (e.target.dataset.id - 0 === cartList[w].id) {
if (cartList[w].number === cartList[w].total)
return alert("库存不足");
cartList[w].number++;
}
}
bindHtml();
}
// 删除
if (e.target.className === "del_item") {
// 0. 防止刁民手滑
if (!confirm('您确定删除吗?')) return
// 1. 更改数据
for (var r = 0; r < cartList.length; r++) {
if (e.target.dataset.id - 0 === cartList[r].id) {
cartList.splice(r, 1)
}
}
// 2. 重新渲染页面
bindHtml()
}
// 清空购物车
if (e.target.className === 'clear') {
// 0. 防止刁民手滑
if (!confirm('您确定删除吗?')) return
// 1. 清空购物车数据
cartList = []
// 2. 重新渲染页面
bindHtml()
}
// 去结算
if (e.target.className === 'pay') {
if (e.target.dataset.num - 0 === 0) return alert('请选中一些商品后在去结算')
alert(e.target.dataset.price)
}
// 删除所有已选中
if (e.target.className === 'del') {
if (e.target.dataset.num === '0') return alert('当前没有已选中的商品')
// 0. 防止刁民手滑
if (!confirm('您确定删除吗?')) return
// 1. 更改数据
for (var t = 0; t < cartList.length; t++) {
if (cartList[t].status) {
cartList.splice(t, 1)
t--
}
}
// 2. 重新渲染页面
bindHtml()
}
// 当没有选中商品的时候禁用 '去结算' 和 '删除所有已选中'
};