💻 【JavaScript】事件相关知识详解🏠专栏:JavaScript
👀个人主页:繁星学编程🍁
🧑个人简介:一个不断提高自我的平凡人🚀
🔊分享方向:目前主攻前端,其他知识也会阶段性分享🍀
👊格言:☀️没有走不通的路,只有不敢走的人!☀️
👉让我们一起进步,一起成为更好的自己!!!🎁
文章目录
【JavaScript】事件概念、绑定、委托、冒泡和阻止默认行为等
一. 概念
事件指的是用户和浏览器之间的交互行为。比如:点击按钮、关闭窗口、鼠标移动等。
我们可以为事件来绑定回调函数来响应事件。绑定事件的方式:
-
可以在标签的事件属性中设置相应的JS代码。
<button onclick="js代码。。。">按钮</button>
-
可以通过为对象的指定事件属性设置回调函数的形式来处理事件。
<button id="btn">按钮</button> <script> var btn = document.getElementById("btn"); btn.onclick = function(){ }; </script>
执行事件的步骤:
- 获取事件源
- 注册事件(绑定事件)
- 添加事件处理程序(采取函数赋值形式)
<div>123</div>
...
<script>
var a = document.querySelector('div');
a.onclick= function(){
console.log('aaa');// aaa
}
</script>
二. 事件绑定
(1) DOM 0级 事件
语法:事件源.on事件类型 = 事件处理函数
特点:同一个事件源的同一个事件类型只能绑定一个事件处理函数
<div></div>
<!----分割线---->
div.onclick = function () {
console.log("绑定了一个点击事件");
};
(2) DOM 2级 事件
语法:事件源.addEventListener(‘事件类型’, 事件处理函数)
特点:可以同一个事件源的同一个事件类型绑定多个事件处理函数,会按照顺序依次触发
<div></div>
<!----分割线---->
div.addEventListener("click", function () {
console.log("绑定一个点击事件");
});
(3) 两种事件绑定的方式区别
DOM 0级 事件:同一个事件源的同一个事件类型只能绑定一个事件处理函数
如果同一个事件源的同一个事件类型绑定多个事件处理函数则后面的处理函数会进行覆盖前面的处理函数
DOM 2级 事件:可以同一个事件源的同一个事件类型绑定多个事件处理函数,会按照顺序依次触发
三. 事件解绑
(1) 解绑dom0级事件
语法:事件源.on事件类型 = null
<button>解绑dom0级事件</button>
<!----分割线---->
var div = document.querySelector("div");
div.onclick = function () {
console.log("绑定0级事件");
};
var btn = document.querySelector("button");
btn.onclick = function () {
div.onclick = null;
};
(2) 解绑dom2级事件
语法:事件源.removeEventListener(‘事件类型’, 要解绑的事件处理函数)
注意:如果用dom2级解绑, 绑定的时候的事件处理函数必须要在外面单独定义,用函数名的形式进行绑定
<button>解绑dom2级事件</button>
<!----分割线---->
var div = document.querySelector("div");
var clickFn = function () {
console.log("绑定dom2级事件");
};
div.addEventListener("click", clickFn);
var btn = document.querySelector("button");
btn.onclick = function () {
div.removeEventListener("click", clickFn);
};
四. 事件对象
当响应函数被调用时,浏览器每次都会将一个事件对象作为实参传递进响应函数中,这个事件对象中封装了当前事件的相关信息,比如:鼠标的坐标,键盘的按键,鼠标的按键,滚轮的方向。
可以在响应函数中定义一个形参,来使用事件对象,但是在IE8以下浏览器中事件对象没有做完实参传递,而是作为window对象的属性保存。
获取事件对象:
- 标准浏览器
在事件处理函数有一个参数,参数就是事件对象
- IE浏览器
IE浏览器天生有一个事件对象 window.event
// eg:
<div></div>
<!----分割线---->
var div = document.querySelector("div");
// 标准浏览器
div.onclick = function (e) {
console.log(e);
};
// IE 浏览器事件对象
div.onclick = function () {
console.log(window.event);
};
// 兼容性操作
div.onclick = function (e) {
var e = e || window.event;
};
五. 鼠标事件对象
(1) client
光标相对于浏览器可视窗口左上角的坐标点
通俗理解就是:鼠标光标相对于肉眼可以看到的浏览器左上角的坐标点
语法:
事件对象.clientX
事件对象.clientY
(2) page
光标相对于文档流左上角的坐标点
通俗理解就是:鼠标光标相对于整个文档左上角的坐标点,就是即使页面向下滑动依然相对于最开始时的左上角坐标点。
语法:
事件对象.pageX
事件对象.pageY
(3) offset
光标相对于准确触发事件的元素左上角的坐标点
通俗理解就是:鼠标光标相对于要触发的元素的左上角的坐标点
语法:
事件对象.offsetX
事件对象.offsetY
三个鼠标事件对象实例:
// eg:
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 200px;
height: 300px;
background-color: pink;
margin: 100px;
margin-top: 700px;
}
</style>
<div></div>
var div = document.querySelector("div");
div.onclick = function (e) {
// 光标相对于 浏览器可视窗口 左上角的坐标点
console.log("e.clientX, e.clientY");
console.log(e.clientX, e.clientY);
console.log("==========================");
// 光标相对于 文档流 左上角的坐标点
console.log("e.pageX, e.pageY");
console.log(e.pageX, e.pageY);
console.log("==================");
// 光标相对于 准确触发事件的元素 左上角的坐标点
console.log("e.offsetX, e.offsetY");
console.log(e.offsetX, e.offsetY);
};
(4) 案例
案例1:实时显示鼠标坐标点
<h2>x坐标 <span class="x">0</span></h2>
<h2>y坐标 <span class="y">0</span></h2>
<!----分割线---->
// 获取元素
var xBox = document.querySelector(".x");
var yBox = document.querySelector(".y");
document.onmousemove = function (e) {
var x = e.clientX;
var y = e.clientY;
// 把 x 和 y 放到span中
xBox.innerHTML = x;
yBox.innerHTML = y;
};
效果图:
案例2:鼠标跟随
分析思路:
/*
案例:鼠标跟随
1. 在什么时候触发效果
鼠标移入
鼠标移出
鼠标移动
2. 在什么范围触发效果
鼠标移入 当前的li
鼠标移出 当前的li
鼠标移动 当前的li
3. 触发的效果是什么
鼠标移入 让当前的li里面的p出现 display: block
鼠标移出 让当前的li里面的p消失 display: none
鼠标移动 p跟着鼠标移动 left top 光标距离准确触发事件的元素的左上角的坐标 offsetX offsetY
一个小BUG
当从右向左滑动, 没问题
当从左向右滑动, p标签抖动
原因:
因为p是li的子元素
当光标在p标签身上,也会触发li的效果 (触发了父元素mousemove效果)
因为offset 一套 是准确触发事件元素的左上角的坐标点
当光标在p身上 , offsetX offsetY获取的是光标距离p左上角的位置
解决:
1. 让光标和p之间有个距离
2. 让p标签保持一个穿透效果 pointer-events: none;
*/
<style>
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
width: 400px;
height: 40px;
border: 1px solid #000;
margin-bottom: 10px;
position: relative;
}
ul {
margin: 100px;
}
p {
width: 200px;
height: 100px;
background-color: skyblue;
position: absolute;
z-index: 999;
top: 10px;
left: 10px;
display: none;
/* 禁止p的鼠标效果 */
pointer-events: none;
}
</style>
<body>
<ul>
<li class="box">
第1个li
<p>第1个li里面的p</p>
</li>
<li>
第2个li
<p>第2个li里面的p</p>
</li>
<li>
第3个li
<p>第3个li里面的p</p>
</li>
</ul>
</body>
<script>
// 0 获取元素
var liList = document.querySelectorAll("li");
// 1 遍历 li给每个添加效果
liList.forEach(function (li) {
// 1-1 鼠标移入
li.addEventListener("mouseover", overHandler);
// 1-2 鼠标移出
li.addEventListener("mouseout", outHandler);
// 1-3 鼠标移动
li.addEventListener("mousemove", moveHandler);
});
// 1-1 鼠标移入事件
function overHandler() {
// 让当前的li里面的p出现
// 找到当前的li ===> 关键字 this
// 找到li里面的p
this.firstElementChild.style.display = "block";
}
// 1-2 鼠标移出
function outHandler() {
// 让当前的li里面的p消失
// 当前的li ==> this
// 当前的li里面的p this.firstElementChild
this.firstElementChild.style.display = "none";
}
// 1-3 鼠标移动
function moveHandler(e) {
// 让 li 里面p 移动
// 获取 鼠标指针距离 li的左上角的值
var x = e.offsetX;
var y = e.offsetY;
this.firstElementChild.style.left = x + "px";
this.firstElementChild.style.top = y + "px";
}
</script>
效果图:
六. 键盘事件对象
事件对象信息
- 按下按个键
按下的是否是组合键
2. 按下的是哪一个按键
事件对象内有一个信息,keyCode
- 按下的是否是组合键
在事件对象中有四个信息
shiftKey
ctrlKey
altKey
metaKey
以上四个信息值都是false, 按下的时候是true
<input type="text" name="" id="" />
<!----分割线---->
var inp = document.querySelector("input");
inp.onkeydown = function (e) {
// 1.获取键盘编码
// console.log(e.keyCode);
// 2.获取功能键
// console.log(e);
// 3.按下的是否是组合键
if (e.keyCode === 65 && e.shiftKey && e.ctrlKey) {
console.log("同时按下 a + shift + ctrl");
}
};
七. 事件传播
当行为发生的时候,会按照 父级 依次向上传递, 直到window
传播的三个阶段:
- 捕获 从window传递到事件目标的过程
- 目标 准确触发事件的元素
- 冒泡 从事件目标传递到window的过程
事件流机制:
- 可以在冒泡阶段触发事件,也可以在捕获阶段触发事件
- 默认是冒泡阶段触发事件
- 完整过程:捕获 -> 目标 -> 冒泡
触发捕获阶段的事件
addEventListener接受三个参数
addEventListener('事件类型', 事件处理函数, true/false)
true 捕获阶段触发
false 冒泡阶段触发(默认)
事件的冒泡(Bubble)
- 事件的冒泡指的是事件向上传导,当后代元素上的事件被触发时,将会导致其祖先元素上的同类事件也会触发;
- 事件的冒泡大部分情况下都是有益的,如果需要取消冒泡,则需要使用事件对象来取消;
- 可以将事件对象的cancelBubble设置为true,即可取消冒泡。
例子:
元素.事件 = function(event){
event = event || window.event;
event.cancelBubble = true;
};
八. 阻止事件传播
- 什么时候需要阻止事件传播?
当我们的父子级结构都有相同的事件类型,就需要阻止事件传播
-
如何阻止事件传播
语法:
事件对象.stopPropagation()
<div></div>
<button>按钮</button>
<!----分割线---->
// 点击 按钮 ,div出现
// 点击 document , div消失
var div = document.querySelector("div");
var btn = document.querySelector("button");
// 由于事件冒泡,btn的点击事件会向上冒泡,触发到document
btn.onclick = function(e) {
// 阻止事件传播
e.stopPropagation();
div.style.display = "block";
console.log(1);
};
document.onclick = function() {
div.style.display = "none";
console.log(2);
};
九. 阻止默认行为
常见的默认行为:
- 表单标签提交
- a标签点击
- 文本选择
- 右键
- …
阻止默认行为
语法 :
// 标准浏览器 事件对象.preventDefault()
// 通用 return false
案例:关闭鼠标右键默认弹窗,并设置自己的弹窗。
<style>
div {
width: 100px;
height: 200px;
background-color: pink;
position: absolute;
display: none;
}
</style>
<body>
<div></div>
</body>
<script>
var div = document.querySelector("div");
// 右键点击
document.oncontextmenu = function (e) {
// 1-1 本身的弹窗不出现 阻止默认行为
e.preventDefault();
// 1-2 div出现
div.style.display = "block";
// 1-3 div的位置和光标的位置一样
var x = e.clientX;
var y = e.clientY;
div.style.left = x + "px";
div.style.top = y + "px";
};
// 2) 左键点击
document.onclick = function () {
div.style.display = "none";
};
</script>
效果图:
十. 事件委托
把子元素的事件交给父元素来绑定
作用:
元素的事件内, 通过事件目标(e.target) 判断准确触发事件的元素
优点:
- 可以减少操作dom的次数。
- 对于后面添加的元素很友好,并对子元素添加事件很有用。
案例
案例1:点击按钮,一次出现5个li,li标签里面有数字, 点击li,背景颜色变为绿色。
<style>
* {
padding: 0;
margin: 0;
}
li {
list-style: none;
width: 50px;
height: 50px;
border: 1px solid #000;
text-align: center;
line-height: 50px;
font-size: 20px;
margin: 10px;
flex-shrink: 0;
background-color: #fff;
}
ul {
padding: 30px;
background-color: orange;
display: flex;
width: 400px;
flex-wrap: wrap;
}
</style>
<body>
<button>点击</button>
<ul>
<li class="box">li1</li>
<li class="box">li2</li>
<li class="box">li3</li>
<li class="box">li4</li>
<li class="box">li5</li>
</ul>
</body>
<script>
var btn = document.querySelector("button");
var ul = document.querySelector("ul");
var count = 6;
btn.onclick = function () {
var str = "";
for (var i = 1; i <= 5; i++) {
str += `<li class='box'>li${count}</li>`;
count++;
}
ul.innerHTML += str;
};
ul.onclick = function (e) {
if (e.target.className === "box") {
e.target.style.background = "green";
}
};
</script>
效果图:
案例2:select下拉
<style>
html,
body {
height: 100%;
overflow: hidden;
}
body,
div,
form,
h2,
ul,
li {
margin: 0;
padding: 0;
}
ul {
list-style-type: none;
}
body {
background: #23384e;
font: 12px/1.5 "微软雅黑";
}
#search,
#search form,
#search .box,
#search .select,
#search a {
background: url(search.jpg) no-repeat;
}
#search,
#search .box,
#search form {
height: 34px;
}
#search {
position: relative;
width: 350px;
margin: 10px auto;
}
#search .box {
background-position: right 0;
}
#search form {
background-repeat: repeat-x;
background-position: 0 -34px;
margin: 0 20px 0 40px;
}
#search .select {
float: left;
color: #fff;
width: 190px;
height: 22px;
cursor: pointer;
margin-top: 4px;
line-height: 22px;
padding-left: 10px;
background-position: 0 -68px;
}
#search a {
float: left;
width: 80px;
height: 24px;
color: #333;
letter-spacing: 4px;
line-height: 22px;
text-align: center;
text-decoration: none;
background-position: 0 -90px;
margin: 4px 0 0 10px;
}
#search a:hover {
color: #f60;
background-position: -80px -90px;
}
#search .sub {
position: absolute;
top: 26px;
left: 40px;
color: #fff;
width: 198px;
background: #2b2b2b;
border: 1px solid #fff;
display: none;
}
#search .sub li {
height: 25px;
line-height: 24px;
cursor: pointer;
padding-left: 10px;
margin-bottom: -1px;
border-bottom: 1px dotted #fff;
}
#search .sub li:hover {
background: #8b8b8b;
}
</style>
<body>
<div id="search">
<div class="box">
<form>
<span id="select" class="select">请选择游戏名称</span>
<a href="javascript:;">搜索</a>
</form>
</div>
<ul id="sub" class="sub">
<li>地下城与勇士</li>
<li>魔兽世界(国服)</li>
<li>魔兽世界(台服)</li>
<li>热血江湖</li>
<li>大话西游II</li>
<li>QQ幻想世界</li>
</ul>
</div>
</body>
<script>
// 1 1) 点击span ul出现
select.onclick = function (e) {
// 阻止事件冒泡
e.stopPropagation();
sub.style.display = "block";
};
// 2. 2) 点击document ul 消失
document.onclick = function () {
sub.style.display = "none";
};
// 3) 通过事件委托的方式 把点击事件添加给ul 获取当前li里面的内容,把内容填到span中
sub.onclick = function (e) {
// 通过事件目标进行判断 e.target
// 判断当前的事件目标是不是li 是的话就执行代码
if (e.target.nodeName === "LI") {
select.innerHTML = e.target.innerHTML;
}
};
</script>
效果图:
案例3:表格即时编辑
<body>
<table border="1">
<tr>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
</tr>
<tr>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
</tr>
<tr>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
</tr>
<tr>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
<td>11111111111111</td>
</tr>
</table>
</body>
<script>
var tdList = document.querySelectorAll("td");
tdList.forEach(function (td) {
td.onclick = function () {
// 当td里面有一个input的时候就不需要触发下面的代码
// 当td里面没有input的时候就需要触发下面的代码
// "td里面没有子元素的时候" td.children.length === 0
if (td.children.length === 0) {
var str = `<input type='text' value=${td.innerHTML}>`;
td.innerHTML = str;
}
// 怎么获取输入框
td.children[0].onblur = function () {
// 输入框失去焦点
var value = td.children[0].value;
td.innerHTML = value;
};
};
});
</script>
效果图:
结束语:
希望对您有一点点帮助,如有错误欢迎小伙伴指正。
👍点赞:您的赞赏是我前进的动力!
⭐收藏:您的支持我是创作的源泉!
✍评论:您的建议是我改进的良药!
一起加油!!!💪💪💪