事件
事件监听
给元素监听事件的三种方式
第一种方式: 事件作为HTML标签的属性:
<标签名 on事件名="代码..."></标签名>
相同的事件如果设置多次,只有前面的生效!相当于html属性的方式
第二种方式: 事件作为元素对象的方法:
元素对象.on事件名 = 回调函数;
相同的事件如果设置多次,最后面的生效!
第三种方式:使用 addEventListenrer 方法:
元素对象.addEventListener('事件名', 回调函数);
相同的事件如果设置多次,都可以生效!
解除事件的监听
第一种和第二种方式监听的事件:
元素对象.on事件名 = null; //把事件的函数 用null覆盖掉了。
第三种方式监听的事件:
元素对象.removeEventListener('事件名', 函数名);
事件流
事件触发的过程分为三个阶段:
捕获阶段: 从 window、document 、html 开始层层向下,直到找到具体发生了事件动作的元素,该元素称为目标元素。
目标阶段: 找到目标元素的那一刻,标志着捕获阶段的结束,冒泡阶段的开始。
冒泡阶段: 从目标元素开始,层层向上,直到 html、document、window, 事件的回调函数默认在冒泡阶段执行
。
注意:事件流也可以只分为捕获阶段和冒泡阶段。
注意: addEventListener 设置第三个参数为 true,该事件会在捕获阶段触发!
事件的回调函数中 this 的指向
this 指向事件监听的元素!
this案例
var liItems = document.querySelectorAll('#box li');
// 遍历出每个 li,监听事件
//使用forEach遍历
liItems.forEach(function(liItem) {
liItem.onclick = function() {
this.classList.toggle('active');
};
});
//使用for循环遍历 这里注意 要用this表示每次循环的元素,不能使用liItems[i]
for (var i = 0; i < liItems.length; i ++) {
liItems[i].onclick = function() {
// liItems[i].classList.toggle('active');
//console.log(i);
this.classList.toggle('active');
};
}
捕获阶段 : 脸被打了:-> 我被打了,-> 上半身被打了,-> 头被打了,-> 脸被打了,找到脸被打了。
冒泡阶段 : 从目标元素开始,层层向上,事件的回调函数默认在冒泡阶段执行。
目标元素:又称目标阶段,具体发生的动作不可以再分的那个。td也属于body中的元素,他下边没有元素了。
常用事件总结
1 鼠标事件
click 单击
dblclick 双击
contextmenu 右击,菜单事件
mousedown 鼠标按键按下
mouseup 鼠标按键抬起
mousemove 鼠标在元素上移动
mouseover 鼠标进入元素
mouseout 鼠标离开元素
mouseenter 鼠标进入元素,用来代替 mouseover,后代元素不会冒泡
mouseleave 鼠标离开元素,用来代替 mouseout,后代元素不会冒泡
mousewheel 滚轮事件,用于 Chrome、Safari、Opear、Edge
DOMMouseScroll 滚轮事件,用于 Firefox,只能通过 addEventListener 监听事件,有大写有小写。
鼠标按键按下和抬起事件如何获取按的是哪个键?
事件对象有button属性,值规则如下:
0 左键
1 滚轮键
2 右键
鼠标移动事件中如何获取鼠标位置?
通过事件对象获取鼠标光标的位置,具有如下属性:
offsetX / offsetY 获取鼠标在目标元素上的位置
clientX / clientY 获取鼠标在视口上的位置
pageX / pageY 获取鼠标在页面上的位置
screenX / screenY 获取鼠标在屏幕上的位置
目标元素不一定就是调用的元素,还有可能是它的子元素,具体发生的动作不可再分的那个。
<div class="box">
对下下自的爱一切学整罪是,为如登和未快千或,俭第畴后守
<p>必锐不得上人同者未玉,人留马能心,战战日非姑论山。</p>
<p>必锐不得上人同者未玉,人留马能心,战战日非姑论山。</p>
</div>
<script>
var box = document.querySelector('.box');
box.onmousedown = function(event){
console.log("鼠标已按下", event.button);
this.style.background = '#900';
};
box.onmouseup = function(){
console.log("鼠标已抬起");
this.style.background = '#099';
};
box.onmousemove = function(event){
this.firstElementChild.innerHTML = "offset:" + event.offsetX + ',' + event.offsetY + '<br>';
};
</script>
滚轮滚动事件兼容性处理:
// Chrome、Safari、Opear、IE
window.onmousewheel = wheelScrollFn;
// Firefox 浏览器
window.addEventListener('DOMMouseScroll', wheelScrollFn);
// 定义滚轮事件的回调函数
function wheelScrollFn(event) {
if (event.wheelDelta) {
// chrome、safari、ie 等
if (event.wheelDelta < 0) {
console.log('滚轮向下滚');
} else {
console.log('滚轮向上滚');
}
} else if (event.detail) {
// firefox 浏览器
if (event.detail > 0) {
console.log('滚轮向下滚');
} else {
console.log('滚轮向上滚');
}
}
}
案例
// 双击 提交表单
btn.ondblclick = function() {
formBox.submit();
};
// 右击 重置表单
btn.oncontextmenu = function() {
formBox.reset();
}
2 键盘事件
keydown 键盘按键按下
keyup 键盘按键抬起
keypress 键盘按键按下
keypress 和 keydown 的区别:
keypress:
控制按键不能触发,只有可输入字符按键才能触发
可以区分字母按键的大小写
keydown:
所有的按键按下都可以触发
无法区分字母按键的大小写
哪些元素可以监听键盘事件?
1. 表单控件元素,获取焦点之后按键盘
2. document对象
如何获取按的是哪个键?
键盘事件对象属性
通过事件对象获取,键盘事件对象(KeyboardEvent)具有如下属性:
keyCode 获取所按按键对应的ascii码,是个数字
which 同keyCode
key 获取所按按键的名字,是个字符串
KeyboardEvent event.key和event.code是JavaScript中用于处理键盘事件的属性。
event.key是一个字符串,表示按下的键的标识符。它提供了一个简单的方式来获取按下的键的信息,例如 "a"、"Enter"、"Shift"等。这个属性可以用来判断用户按下了哪个键,并根据按下的键执行相应的操作。
event.code是一个字符串,表示按下的键的物理键码。它提供了一个标准化的方式来获取按下的键的信息,不受键盘布局的影响。例如,无论用户使用的是QWERTY键盘还是AZERTY键盘,按下的键的event.code都是相同的。这个属性可以用来处理需要考虑键盘布局的情况,例如游戏中的按键操作
键盘事件案例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
input {
width: 300px;
padding: 10px;
border: 1px solid #999;
outline: none;
}
#res {
padding: 10px 20px;
font-size: 1.5em;
}
</style>
</head>
<body>
<div>请输入银行卡号:</div>
<input type="text" id="bankCard">
<div id="res"></div>
<script>
(function(){
// 获取元素
var inputBox = document.querySelector('#bankCard');
var resBox = document.querySelector('#res');
// 给输入框元素监听按键抬起事件
inputBox.onkeyup = function() {
resBox.innerHTML = inputBox.value;
}
})()
</script>
</body>
</html>
3 文档事件
load 文档加载完毕,需要监听到window或者body元素
DOMContentLoaded 文档加载完毕,需要监听到window或者body元素
load 事件与 DOMContentLoaded 事件的区别:
load: 文档中所有的一切加载完毕,包括引用的外部文件
DOMContentLoaded: 文档中元素加载完毕,不包括引用的外部文件,只能使用addEventListener监听事件
4 表单事件
submit 表单提交的时候,需要监听到form元素上
reset 表单重置的时候,需要监听到form元素上
blur 失去焦点的时候,需要监听到表单控件元素
focus 获取焦点的时候,需要监听到表单控件元素
select 里面的文字内容被选中的时候,需要监听到输入框或文本域元素上
input 输入框内容改变,需要监听到输入框或文本域元素上
change 监听到输入框元素,输入的内容改变且失去焦点
change 监听到选择框元素,一改变就触发
change案例
(function(){
// 定义地址信息
var provList = ['江苏', '浙江', '安徽', '河南'];
var cityList = [
['南京', '苏州', '徐州', '无锡', '常州'],
['杭州', '宁波', '温州', '绍兴', '湖州'],
['合肥', '芜湖', '滁州', '马鞍山', '黄山'],
['郑州', '洛阳', '开封', '安阳', '许昌'],
];
// 获取元素
var provSel = document.querySelector('#provSel');
var citySel = document.querySelector('#citySel');
// 根据数据 添加省的选项
provList.forEach(function(item, index) {
provSel.add(new Option(item, index));
});
// 监听省的选项发生改变
provSel.onchange = function() {
// 获取当前所选择的选项
var index = this.selectedIndex;
// 清空目前城市的下拉选项
citySel.options.length = 0;
// 取出城市信息 添加为选项
cityList[index].forEach(function(item, index) {
citySel.add(new Option(item, index))
});
};
// 手动调用change事件的回调函数
provSel.onchange();
})()
5 图片事件
load 图片加载完毕
error 图片加载失败
网页加载界面 CSS
.mask-box {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #333;
display: flex;
transition: top 1s;
}
.mask-inner {
margin: auto;
width: 60%;
}
.progress-box {
height: 20px;
border: 1px solid #fff;
}
.progress-content {
height: 20px;
background: #fff;
width: 0%;
}
.progress-num {
margin-top: 10px;
color: #fff;
}
网页加载界面 JS
(function(){
// 获取相关元素
var maskBox = document.querySelector('#maskBox');
var progressContent = maskBox.querySelector('.progress-content');
var progressNum = maskBox.querySelector('.progress-num');
// 创建数组 保存页面中高清大图的地址
var imgData = [
'https://cdn.pixabay.com/photo/2023/04/24/03/16/camping-7947056_960_720.jpg',
'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_960_720.jpg',
'https://cdn.pixabay.com/photo/2015/06/19/21/24/avenue-815297_960_720.jpg',
'https://cdn.pixabay.com/photo/2015/12/01/20/28/forest-1072828_960_720.jpg',
'https://cdn.pixabay.com/photo/2017/02/01/22/02/mountain-landscape-2031539_960_720.jpg',
'https://cdn.pixabay.com/photo/2013/10/02/23/03/mountains-190055_960_720.jpg',
'https://cdn.pixabay.com/photo/2016/08/11/23/48/mountains-1587287_960_720.jpg',
'https://cdn.pixabay.com/photo/2016/12/11/12/02/mountains-1899264_960_720.jpg',
];
// 定义变量记录已经加载的图片的数量
loadedImgNum = 0;
// 遍历所有的图片
imgData.forEach(function(item) {
// 创建img元素
var imgEle = new Image();
imgEle.src = item;
// 给新img元素监听load事件
imgEle.onload = function(){
loadedImgNum ++; // 已加载图片数量 +1
var progress = loadedImgNum / imgData.length; // 计算当前进度
// 进度条变化
progressContent.style.width = progress * 100 + '%';
// 显示进度条数字
progressNum.innerHTML = progress * 100 + '%';
// 判断加载完毕 页面画上去
if (progress >= 1) {
maskBox.style.top = '-100%';
}
}
})
})()
6 过渡事件
transitionstart 过渡开始事件,过渡延迟之后触发,有几个过渡属性触发几次
transitionrun 过渡开始事件,过渡延迟之前触发,一点击就触发
transitionend 过渡结束事件
<style>
#box {
width: 200px;
height: 200px;
background: #900;
transition: 2s 3s;
}
#box.active {
border-radius: 50%;
background: #099;
transform: rotate(720deg);
}
<body>
<div id="box"></div>
<script>
// 获取元素
var box = document.querySelector('#box');
// 监听单击事件
box.onclick = function() {
this.classList.toggle('active');
};
// 过渡开始
box.ontransitionstart = function(){
console.log('transitionstart');
};
box.ontransitionrun = function(){
console.log('transitionrun');
};
// 过渡结束
box.ontransitionend = function() {
console.log('transitionend');
};
7 动画事件
animationstart 动画开始事件,延迟之后触发
animationend 动画结束之后
animationiteration 动画每执行一次就触发一次
<style>
@keyframes boxScroll {
100% {
transform: translate(1000px, 200px) rotate(3600deg);
}
}
#box {
width: 100px;
height: 100px;
background: #900;
color: #fff;
font-size: 30px;
text-align: center;
line-height: 100px;
animation: boxScroll 2s 1s infinite alternate;
}
</style>
</head>
<body>
<div id="box">
动画
</div>
<script>
// 获取元素
var box = document.querySelector('#box');
// 动画开始事件
box.onanimationstart = function(){
console.log('animationstart');
};
// 动画结束事件
box.onanimationend = function() {
console.log('animationend');
};
box.onanimationiteration = function() {
console.log('animationiteration');
}
</script>
8 其他事件
scroll 滚动事件,需要监听给内容可以滚动的元素或者window
resize 视口尺寸改变事件, 需要监听给window
9 离开窗口事件
document.onvisibilitychange = function() {
if (document.hidden) {
// 正在离开本窗口 停止定制器
clearInterval(intervalId);
} else {
// 再次回到本窗口 再次开启定时器
intervalId = setInterval(next, duration);
}
}
onvisibilitychange 监听页面窗口事件
属性
document.hidden 离开窗口 返回true/false
滚动事件案例
<style>
#box {
width: 600px;
height: 250px;
padding: 20px;
background: #099;
overflow: auto;
}
#box p {
padding: 10px;
background: #ccc;
}
</style>
<script>
// 获取box元素
var box = document.querySelector('#box');
// 监听滚动事件
box.onscroll = function() {
console.log('box中的内容滚动:', box.scrollTop);
};
// 给window监听 页面在视口中滚动的时候
window.onscroll = function() {
console.log('页面滚动:', document.documentElement.scrollTop)
}
</script>
视口变化事件案例
<body>
<script>
window.onresize = function() {
console.log(document.documentElement.clientWidth, document.documentElement.clientHeight);
}
</script>
</body>
Event 对象
在触发 DOM 上的某个事件时,会产生一个事件对象 event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。
获取 Event 对象
浏览器会将一个 event 对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0 级或 DOM2 级),都会传入 event 对象。
浏览器每一个时刻只能触发一个事件,有先有后。触发事件的那一刻,全局window的event属性值就是那个事件的对象。
给事件的回调函数设置形参,自动获取到事件对象
鼠标事件对象 MouseEvent 的属性和方法
button 按键值, 0表示左键,1表示滚轮键,2表示右键
offsetX / offsetY 获取鼠标在目标元素上的位置
clientX / clientY 获取鼠标在视口上的位置
pageX / pageY 获取鼠标在页面上的位置
screenX / screenY 获取鼠标在屏幕上的位置
wheelDelta 获取鼠标滚轮的上下滚动位置 chrome的用法
detail 获取鼠标滚轮的上下滚动位置 火狐浏览器的用法
// 获取元素
var box = document.querySelector('#box');
box.onclick = function(event) {
var event = event || window.event; // 兼容IE的用法,或运算符:如果第一个成功,不执行第二个
console.log('click', event);
};
box.addEventListener('contextmenu', function(event) {
console.log('contextmenu', event);
});
box.onmousedown = bindMove;
function bindMove(event) {
console.log('鼠标移动:', event);
}
键盘事件对象 KeyborardEvent 的属性和方法
keyCode 获取按键对应的ascii码,是个数字
which 同keyCode
key 获取按键的名字,是个字符串
控制元素移动的js代码案例
(function(){
// 获取元素
var box = document.querySelector('#box');
var tank = document.querySelector('#tank');
// 监听键盘事件
document.onkeydown = function(event) {
// 判断所按按键
switch (event.keyCode) {
case 37: // 向左
tank.style.transform = 'rotate(-90deg)';
tank.style.left = Math.max(tank.offsetLeft - 5, 0) + 'px';
break;
case 38: // 向上
tank.style.transform = 'rotate(0deg)';
tank.style.top = Math.max(tank.offsetTop - 5, 0) + 'px';
break;
case 39: // 向右
tank.style.transform = 'rotate(90deg)';
tank.style.left = Math.min(tank.offsetLeft + 5, box.clientWidth-tank.offsetWidth) + 'px';
break;
case 40: // 向下
tank.style.transform = 'rotate(180deg)';
tank.style.top = Math.min(tank.offsetTop + 5, box.clientHeight-tank.offsetHeight) + 'px';
break;
}
};
})();
所有类型的事件对象都有的属性和方法
type 获取事件名
timeStamp 获取事件触发时的时间戳(从页面打开的那一刻开始算)
target 获取目标元素。具体发生的动作元素,且是不可再分的元素。
this 指向监听事件的元素
stopPropagation() 阻止事件冒泡
preventDefault() 阻止浏览器默认行为
阻止事件冒泡
事件对象.stopPropagation() //阻止了 上层元素的调用
浏览器的默认行为
① 浏览器有哪些默认行为
1. 点击超链接跳转
2. 点提交按钮或按回车键表单可以提交; 点重置按钮表单重置
3. 鼠标右键弹出系统菜单
4. 滚动滚轮页面滚动
...
② 阻止浏览器默认行为
1. 事件对象.preventDefault()
2. 如果是第二种事件监听方式, return false 也可以阻止浏览器默认行为
事件委托(事件委派)
事件委托,就是把一个元素响应事件的函数委托到另一个元素。
一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
我们可以使用 event 对象中的 target 属性来判断目标元素。
//给一个一直都存在的元素绑定事件
box.onclick = function(event){
//判断 如果点击的 .item元素
if (event.target.className.indexOf('item') >= 0) {
event.target.classList.toggle('active');
}
};
事件委托的原理:
1. 将事件监听到某个祖先元素
2. 在事件的回调函数进行判断, 只有目标元素是指定的元素才进行相应的操作
可以使用 event.target 获取目标元素,类名、标签名都可以作为判断依据
事件委托能解决什么问题?
1. 让新增加的元素也具有事件
2. 如果需要给大量的元素监听事件,使用事件委托可以提升效率,减少内存
事件委托案例
<script>
// 获取相关元素
var todoContent = document.querySelector('#todoContent');
var inputBox = document.querySelector('#input');
var addBtn = document.querySelector('#addBtn');
// 获取所有的删除按钮
var deleteBtns = todoContent.querySelectorAll('.delete');
// 点击添加按钮
addBtn.onclick = function() {
// 创建新的元素
var newLi = document.createElement('li');
// 设置新LI中的文本内容
newLi.innerHTML = inputBox.value + '<span class="delete">×</span>';
// 给ul添加子节点
todoContent.appendChild(newLi);
// 清空输入框
inputBox.value = '';
};
// 将删除按钮的单击事件 委托到 todoContent
todoContent.onclick = function(event) {
// 如果目标元素是删除按钮 才执行相应操作
// console.log(event.target.className);
// console.log(event.target.nodeName);
// console.log(event.target.tagName);
if (event.target.className === 'delete') {
// 删除 删除按钮所在的 li
todoContent.removeChild(event.target.parentElement);
}
};
</script>
事件对象的原型链关系
以鼠标事件对象为例:
鼠标事件对象 -> MouseEvent.prototype -> UIEvent.prototype -> Event.prototype -> Object.prototype
事件的节流
在400ms以内不允许再触发事件
event.time event对象添加time属性(event对象中没有time属性)
event.timeStamp 获取事件的时间戳 时间戳:打开页面的那一刻
事件的防抖和节流
防抖 debounce
什么是防抖?
持续触发一个事件时,在n秒内,事件没有再次触发,此时才会执行事件回调函数;如果n秒内,又触发了事件,就重新计时。
一直点击,一直不触发,松手后2s后才触发。
防抖的应用场景?
- 搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- window 触发 resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
- 点赞、提交表单等,防止多次提交
节流 throttle
什么是节流?
在一个单位时间内,不论触发一次事件还是多次事件,只能执行一次。
节流的应用场景:
- 拖拽,固定时间内只执行一次,防止高频率的触发位置变动
- 监听滚动事件,比如是否滑到底部自动加载更多,用节流节省请求资源,多次请求合并为一个请求。
封装防抖和节流的高阶函数
防抖
function debounce(method, delay) {
var timeId;
return function() {
var that = this;
var args = arguments;
clearTimeout(timeId);
timeId = setTimeout(function () {
method.apply(that, args);
}, delay)
}
}
把事件的回调函数作为参数,传入进去,然后会给你return一个防抖的新函数。
单位时间内事件又触发,往后推。
// 获取元素
var btn = document.querySelector('#btn');
// 定义定时器标记
var timeId = null;
// 监听点击事件
btn.onclick = function() {
// 先清空上一次定时器
if(timeId){
clearTimeout(timeId);
}
// 开启新的定时器
timeId = setTimeout(function() {
console.log('click 触发了,', Date.now());
}, 1000);
};
这个事件在1s后执行,如果不想让这个事件触发,就需要一直点击,清除定时器,当超过1s后再点击,这个事件已经执行,清除定时器没有意义。
节流
function throttle(method, delay) {
var prev = Date.now();
return function() {
var that = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
method.apply(that, args);
prev = now;
}
}
};
把事件的回调函数作为参数,传入进去,然后会给你return一个防抖的新函数。
// 定义变量 记录上一次触发事件的时间,打开页面的时间戳
var prev = Date.now();
// 监听事件
window.onscroll = function() {
// 记录此时此刻的时间戳
var now = Date.now();
// 判断事件间隔
if (now - prev > 1000) {
console.log('加载新数据...', Date.now());
prev = now;
}
}
防抖案例
<body>
<input type="text" id="input01">
<input type="text" id="input02">
<script>
// 获取元素
var inputBox01 = document.querySelector('#input01');
var inputBox02 = document.querySelector('#input02');
// 监听事件
inputBox01.oninput = changeInput;
// 定义事件的回调函数
function changeInput(ev) {
console.log('向后端请求搜索数据...', this.value, ev);
};
/*
1. 两个参数 没有防抖功能的函数,时间间隔
2. 返回值是函数
3. 返回的函数中调用 method, 使用apply,为了设置 method 中的this是监听事件的元素
4. 保证 method 中能够获取到事件对象, 通过 apply 传递进去
*/
// 防抖函数 版本1
function debounce(method, delay) {
var timeId = null;
return function() {
var that = this;
clearTimeout(timeId);
timeId = setTimeout(function() {
method.call(that);
}, delay);
};
}
// 监听事件 debounce 生产一个具有防抖功能的新函数
inputBox02.oninput = debounce(changeInput, 400);
// 防抖函数 版本2
function debounce(method, delay) {
var timeId = null;
return function() {
//return这里的函数才是事件调用地方,在这里才可以拿到事件的对象。
var that = this;
var args = arguments;
clearTimeout(timeId);
timeId = setTimeout(function() {
//这里不能直接写this,argument,因为这里是定时器对象的回调函数。
method.apply(that, args);
}, delay);
};
}
</script>
</body>
节流案例
<body>
<div id="box01" class="box"></div>
<div id="box02" class="box"></div>
<script>
// 获取元素
var box01 = document.querySelector('#box01');
var box02 = document.querySelector('#box02');
// 监听事件
box01.onmousemove = moveFunc;
box02.onmousemove = throttle(moveFunc, 600);
function moveFunc(ev) {
console.log('鼠标移动', Date.now());
this.innerHTML = ev.offsetX + ',' + ev.offsetY;
}
// 节流函数
function throttle(method, delay) {
var prev = Date.now(); // 记录上一次事件执行事件
return function() {
var now = Date.now(); // 此时此刻的时间
// 比上一次执行大于时间间隔才能执行
if (now - prev > delay) {
method.apply(this, arguments);
prev = now; // 重新记录执行时间
}
}
}
</script>
</body>