1.事件对象
1.1 获取事件对象
1.1.1.事件对象是什么
也是个对象,这个对象里有事件触发时的相关信息
例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息
1.1.2.如何获取
在事件绑定的回调函数的第一个参数就是事件对象
一般命名为event、ev、e
1.1.3.获取事件对象语法:
//获取事件对象语法:
//1.获取元素
let 获取的元素 = document.querySelector()
//2.事件监听 在事件绑定的回调函数的第一个参数就是事件对象
获取的元素.addEventListener('事件',function(e){
})
例:获取标签 button的事件对象,并输出观察事件对象
//获取标签 button的事件对象,并输出观察事件对象
//1.获取button标签
let button = document.querySelector('button')
//2.获取button标签的事件对象
button.addEventListener('click',function(e){
console.log(e)
})
1.2.部分常用属性
(1)type
获取当前的事件类型
(2)clientX/clientY
获取光标相对于浏览器可见窗口左上角的位置
(3)offsetX/offsetY
获取光标相对于当前DOM元素左上角的位置
(4)key
用户按下的键盘键的值
现在不提倡使用keyCode
(5)PageX/PageY
获取光标相对于HTML文档的位置坐标
例:触发整个HTML文档点击事件,观察事件对象clientX/clientY、pageX/pageY以及offsetX/offsetY的坐标变化
<!DOCTYPE html>
<html lang="en">
<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>
body{
height: 3000px;
}
div{
width: 300px;
height: 300px;
background-color: red;
margin: 200px;
}
</style>
</head>
<body>
<div></div>
<script>
//触发整个HTML文档点击事件,观察事件对象clientX/clientY、pageX/pageY以及offsetX/offsetY的坐标变化
document.addEventListener('click',function (e){
// console.log(11);
// pageX和PageY 跟文档坐标有关系
//相对浏览器可视区域
console.log('clientX:' + e.clientX,'clientY:'+e.clientY)
//相对html文档的坐标
console.log('pageX:' + e.pageX,'pageY:'+e.pageY)
//相对于浏览器中DOM元素,本例中相对div标签
console.log('offsetX:' + e.offsetX,'offsetY:'+e.offsetY)
})
</script>
</body>
</html>
全屏窗口任意位置点击
缩小窗口任意位置点击
1.3.获取事件对象案例
1.3.1.跟随鼠标案例
需求:
一张图片一直跟着鼠标移动
代码与思路:
<!DOCTYPE html>
<html lang="en">
<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>
img{
position: absolute;
left: 0px;
top: 0px;
}
</style>
</head>
<body>
<img src="./images/tianshi.gif" alt="">
<script>
//0.html中引入图片标签,css中初始设置图片相对网页的位置
//1.获取图片标签
let pic = document.querySelector('img')
//2.事件监听:鼠标移动事件,事件源:网页文档(document)页面被触发,图片随着鼠标移动
//3.获取事件对象,修改图片定位,把事件对象中的PageX和PageY属性给相应的定位
document.addEventListener('mousemove',function(e){
pic.style.left = e.pageX - 48 + 'px'
pic.style.top = e.pageY- 40 + 'px'
})
</script>
</body>
</html>
细节:
1.要移动图片必须先设置定位,图片是相对某个位置进行移动
2.把事件对象中的PageX和PageY属性给相应的定位要加像素单位,否则图片无法移动
把事件对象中的PageX和PageY属性给相应的定位,不加像素单位,效果如下:
pic.style.left = e.pageX - 48
pic.style.top = e.pageY- 40
1.3.2.案例:按下键盘中的Enter键,发布微博内容文本
核心代码思路分析:
事件源:微博输入文本内容框textarea
事件:按下键盘中的entr键(keyup或keydown事件)
中心思路:知道按下的是Enter键的键盘事件,并能发布微博
思路:
1.获取文本输入框标签textarea
2.注册事件:按下键盘中的entr键(keyup或keydown事件)
3.调用事件对象e中的key属性,判断用户在文本框中按下的是否为enter键,若是,则发布微博
3.1.调用send中的click接口,自动触发send中的点击按钮,也可将发布微博代码复制到这里即可
核心代码:
//案例:按下键盘中的Enter键,发布微博内容文本
//事件源:微博输入文本内容框textarea
//事件:按下键盘中的entr键(keyup或keydown事件)
// 中心思路:知道按下的是Enter键的键盘事件,并能发布微博
//思路:
//1.获取文本输入框标签textarea
//2.注册事件:按下键盘中的entr键(keyup或keydown事件)
//建议使用keyup,keydown在微博发布后换行发布另一个新的微博,无法回到微博输入起始位置再发布新的微博,而keyup在微博发布后可以回到微博输入起始位置发布新的微博
textarea.addEventListener('keyup',function(e){
//测试注册事件是否成功
// alert(123)
//输入框中按下enter,观察事件对象e的属性,e的属性中key可以看到按下的是enter键
// console.log(e)
//3.调用事件对象e中的key属性,判断用户在文本框中按下的是否为enter键,若是,则发布微博
if(e.key === 'Enter'){
//3.1.调用send中的click接口,自动触发send中的点击按钮,也可将发布微博代码复制到这里即可
send.click()
}
})
观察:
输入框中按下enter,观察事件对象e的属性,e的属性中key可以看到按下的是enter键
运行结果:
keyup与keydown使用上视觉效果区别:
1.keydown在微博发布后换行发布另一个新的微博,无法回到微博输入起始位置再发布新的微博;
2.keyup在微博发布后可以回到微博输入起始位置发布新的微博
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>微博发布</title>
<style>
* {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
.w {
width: 900px;
margin: 0 auto;
}
.controls textarea {
width: 878px;
height: 100px;
resize: none;
border-radius: 10px;
outline: none;
padding-left: 20px;
padding-top: 10px;
font-size: 18px;
}
.controls {
overflow: hidden;
}
.controls div {
float: right;
}
.controls div span {
color: #666;
}
.controls div .useCount {
color: red;
}
.controls div button {
width: 100px;
outline: none;
border: none;
background: rgb(0, 132, 255);
height: 30px;
cursor: pointer;
color: #fff;
font: bold 14px '宋体';
transition: all 0.5s;
}
.controls div button:hover {
background: rgb(0, 225, 255);
}
.controls div button:disabled {
background: rgba(0, 225, 255, 0.5);
}
.contentList {
margin-top: 50px;
}
.contentList li {
padding: 20px 0;
border-bottom: 1px dashed #ccc;
position: relative;
}
.contentList li .info {
position: relative;
}
.contentList li .info span {
position: absolute;
top: 15px;
left: 100px;
font: bold 16px '宋体';
}
.contentList li .info p {
position: absolute;
top: 40px;
left: 100px;
color: #aaa;
font-size: 12px;
}
.contentList img {
width: 80px;
border-radius: 50%;
}
.contentList li .content {
padding-left: 100px;
color: #666;
word-break: break-all;
}
.contentList li .the_del {
position: absolute;
right: 0;
top: 0;
font-size: 28px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="w">
<!-- 操作的界面 -->
<div class="controls">
<img src="./images/9.6/tip.png" alt="" /><br />
<!-- maxlength 可以用来限制表单输入的内容长度 -->
<textarea placeholder="说点什么吧..." id="area" cols="30" rows="10" maxlength="200"></textarea>
<div>
<span class="useCount" id="useCount">0</span>
<span>/</span>
<span>200</span>
<button id="send">发布</button>
</div>
</div>
<!-- 微博内容列表 -->
<div class="contentList">
<ul id="list">
<!-- 添加了hidden属性元素会直接隐藏掉 -->
<!-- <li >
<div class="info">
<img class="userpic" src="./images/9.6/03.jpg" />
<span class="username">死数据:百里守约</span>
<p class="send-time">死数据:发布于 2020年12月05日 00:07:54</p>
</div>
<div class="content">死数据:111</div>
<span class="the_del">X</span>
</li> -->
</ul>
</div>
</div>
<script>
// maxlength 是一个表单属性, 作用是给表单设置一个最大长度
// 模拟数据
let dataArr = [
{ uname: '司马懿', imgSrc: './images/9.5/01.jpg' },
{ uname: '女娲', imgSrc: './images/9.5/02.jpg' },
{ uname: '百里守约', imgSrc: './images/9.5/03.jpg' },
{ uname: '亚瑟', imgSrc: './images/9.5/04.jpg' },
{ uname: '虞姬', imgSrc: './images/9.5/05.jpg' },
{ uname: '张良', imgSrc: './images/9.5/06.jpg' },
{ uname: '安其拉', imgSrc: './images/9.5/07.jpg' },
{ uname: '李白', imgSrc: './images/9.5/08.jpg' },
{ uname: '阿珂', imgSrc: './images/9.5/09.jpg' },
{ uname: '墨子', imgSrc: './images/9.5/10.jpg' },
{ uname: '鲁班', imgSrc: './images/9.5/11.jpg' },
{ uname: '嬴政', imgSrc: './images/9.5/12.jpg' },
{ uname: '孙膑', imgSrc: './images/9.5/13.jpg' },
{ uname: '周瑜', imgSrc: './images/9.5/14.jpg' },
{ uname: '老夫子', imgSrc: './images/9.5/15.jpg' },
{ uname: '狄仁杰', imgSrc: './images/9.5/16.jpg' },
{ uname: '扁鹊', imgSrc: './images/9.5/17.jpg' },
{ uname: '马可波罗', imgSrc: './images/9.5/18.jpg' },
{ uname: '露娜', imgSrc: './images/9.5/19.jpg' },
{ uname: '孙悟空', imgSrc: './images/9.5/20.jpg' },
{ uname: '黄忠', imgSrc: './images/9.5/21.jpg' },
{ uname: '百里玄策', imgSrc: './images/9.5/22.jpg' },
]
// 需求1
// 1. 注册input事件
// 2. 将文本的内容的长度赋值给对应的数值
// 3. 表单的maxlength属性可以直接限制在200个数之间
//思路:
//1.获取文本内容textarea标签和控制字数变化的useCount盒子标签
let textarea = document.querySelector('textarea')
let useCount = document.querySelector('#useCount')
//2.事件监听,input事件,文本内容值得长度 value.length发生变化
textarea.addEventListener('input',function(){
// console.log(textarea.value.length)
useCount.innerHTML = textarea.value.length
//优化:若用户输入多个空格,控制字数变化的useCount的值为0
//判断用户输入字符为空或空格,修改控制字数变化字符的innerHTML设置为0
if(textarea.value.trim() === ''){
useCount.innerHTML = 0
}
})
//需求2
// 克隆预定义好的模板,将模板的hidden属性设置为false, 并最终展示到页面上
// 判断如果内容为空,则提示不能输入为空, 并且直接return
// 防止输入无意义空格, 使用字符串.trim()去掉首尾空格, 并将表单的value值设置为空字符串
//总体思路:
//点击发布,判断输入内容不能为空,并将去掉首尾空格, 并将表单的value值设置为空字符串
//详细思路
//1.获取发送按钮标签
//2.事件监听,点击发布,事件监听用户输入内容value值是否为空 ,返回提示输入输入内容不能为空,请重新输入的提示;
//3.使用字符串.trim()去掉首尾空格, 并将表单的value值设置为空字符串
let send =document.querySelector('button')
send.addEventListener('click',function(){
if(textarea.value.trim() === ''){
//细节:
//1.为什么要去空格?
//若输入多个空格,点击发布也无法判断输入为空,不能给出输入不能为空,请重新输入的提示;
//2.去除空格方法,字符串.trim()
// 防止输入无意义空格, 使用字符串.trim()去掉首尾空格
// console.log(' str')
// console.log(' str '.trim())
return alert('输入内容不能为空,请重新输入')
}
//需求3
//获取文本域的内容, 赋值给由模板克隆出来的新标签里面的content.innerText
// 随机获取数据数组里面的内容, 替换newNode的图片和名称
// 利用时间对象将时间动态化 new Date().toLocaleString()
//业务明确:必须是用户输入内容并点击发布后,在页面才能显示用户自己发布的各种信息,所以这个需求应当写在监听微博发布事件函数内,即:这个需求写在需求2的发布微博事件监听中
//1.获取父级标签,在父级ul中创建并后面追加li标签和文本内容
//1.1.获取父级标签
let list = document.querySelector('#list')
//错误:1.1创建父节点list的ul盒子和子节点li
// let list = document.createElement('#list')
//1.2.创建子节点
let li = document.createElement('li')
//2.声明和调用随机函数,生成微博发布后的随机用户信息
//2.1.声明随机函数
function randomGet (min,max){
return Math.floor(Math.random()*(max- min + 1))+min
}
//2.2.调用函数
let random = randomGet(0,dataArr.length-1)
//3.修改li标签的文本内容,随机替换新子节点的文本内容,利用时间对象将时间动态化
li.innerHTML = `
<div class="info">
<img class="userpic" src = ${dataArr[random].imgSrc} />
<span class="username">${dataArr[random].uname}</span>
<p class="send-time">${new Date().toLocaleString()}</p>
</div>
<div class="content">${textarea.value}</div>
<span class="the_del">X</span>`
// 4.追加节点 父元素.insertBefore(子元素, 放到那个元素的前面)
list.insertBefore(li,list.children[0])
//需求5
// 将表单域内容重置为空
// 将userCount里面的内容重置为0
//1.将输入内容定为空字符串
textarea.value = ''
//2.将控制输入字数字符的innerHTML定为0
useCount.innerHTML = 0
// 需求4
// 在事件处理函数里面获取点击按钮,注册点击事件
// (易错点: 必须在事件里面获取,外面获取不到)
// 删除对应的元素 (通过this获取对应的那条需要删除的元素)
//业务明确:只有在生成微博内容的li后,才会有删除标志X的标签
//1.获取删除标签,注册点击删除事件,其中删除事件是移除li标签以及里面的所有内容
let del = document.querySelector('.the_del')
del.addEventListener('click',function(){
//2.删除节点的语法 父元素.removeChild(子元素)
list.removeChild(li)
//错误示例:父元素.removeChild(父元素.chilren[子元素数组序号])
// list.removeChild(list.children[random])
})
})
//案例:按下键盘中的Enter键,发布微博内容文本
//事件源:微博输入文本内容框textarea
//事件:按下键盘中的entr键(keyup或keydown事件)
// 中心思路:知道按下的是Enter键的键盘事件,并能发布微博
//思路:
//1.获取文本输入框标签textarea
//2.注册事件:按下键盘中的entr键(keyup或keydown事件),
//建议使用keyup,keydown在微博发布后换行发布另一个新的微博,无法回到微博输入起始位置再发布新的微博,而keyup在微博发布后可以回到微博输入起始位置发布新的微博
textarea.addEventListener('keyup',function(e){
//测试注册事件是否成功
// alert(123)
//输入框中按下enter,观察事件对象e的属性,e的属性中key可以看到按下的是enter键
// console.log(e)
//3.调用事件对象e中的key属性,判断用户在文本框中按下的是否为enter键,若是,则发布微博
if(e.key === 'Enter'){
//3.1.调用send中的click接口,自动触发send中的点击按钮,也可将发布微博代码复制到这里即可
send.click()
}
})
// textarea.addEventListener('keydown',function(e){
// //测试注册事件是否成功
// // alert(123)
// //输入框中按下enter,观察事件对象e的属性,e的属性中key可以看到按下的是enter键
// // console.log(e)
// //3.调用事件对象e中的key属性,判断用户在文本框中按下的是否为enter键,若是,则发布微博
// if(e.key === 'Enter'){
// //3.1.调用send中的click接口,自动触发send中的点击按钮,也可将发布微博代码复制到这里即可
// send.click()
// }
// })
</script>
</body>
</html>
2.事件流
2.1 事件流和两个阶段说明
事件流指的是事件完整执行过程中的流动路径
说明:假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段
简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父
2.2 事件捕获和事件冒泡
2.2.1.事件冒泡
概念:
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡
简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件
事件冒泡是默认存在的。
例:网页文档中有一个子级div的son 标签和它的父级div标签,
在子级son、父级father和爷爷级html文档document中都设置鼠标点击click事件,
点击子级son,观察事件冒泡,
点击父级father和爷爷级html文档document,观察是否有子级事件发生。
<!DOCTYPE html>
<html lang="en">
<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>
.father{
margin: 100px auto;
width: 600px;
height: 600px;
background-color: red;
}
.son{
width: 100px;
height: 100px;
background-color:blue;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
//1.获取父级和子级标签
let father = document.querySelector('.father')
let son = document.querySelector('.son')
//2.给父级和子级都注册点击事件
father.addEventListener('click',function(){
alert(' I am father!')
})
son.addEventListener('click',function(){
alert(' I am son!')
})
document.addEventListener('click',function(){
alert(' I am grandfather!')
})
</script>
</body>
</html>
(3).运行结果:
(1).点击子级son:
先执行弹出子级son的内容,再执行弹出父级father的内容,最后弹出爷爷级html文档document的内容
(2).点击父级father:
执行弹出本级father的内容,最后弹出爷爷级html文档document的内容
(3).点击爷爷级html文档document:
弹出本级的内容
2.2.2.事件捕获
概念:
从DOM的根元素开始去执行对应的事件 (从外到里)
事件捕获需要写对应代码才能看到效果
代码:
dom元素.addEventListener('事件',事件处理函数,是否使用捕获机制(注:值为true))
说明:
1.addEventListener第三个参数传入true代表是捕获阶段触发(很少使用);
2.若传入false代表冒泡阶段触发,默认就是false;
3.若是用 L0 事件监听,则只有冒泡阶段,没有捕获,例:
let btn = document.querySelector('button')
btn.onclick = function(){}
例:网页文档中有一个子级div的son 标签和它的父级div标签,
在子级son、父级father和爷爷级html文档document中都设置鼠标点击click事件,
点击子级son,观察事件捕获,
点击父级father和爷爷级html文档document,观察是否有子级事件发生。
<!DOCTYPE html>
<html lang="en">
<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>
.father{
margin: 100px auto;
width: 600px;
height: 600px;
background-color: red;
}
.son{
width: 100px;
height: 100px;
background-color:blue;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
//捕获事件语法
// dom元素.addEventListener('事件',事件处理函数,是否使用捕获机制(注:值为true))
//1.获取父级和子级标签
let father = document.querySelector('.father')
let son = document.querySelector('.son')
//2.给父级和子级都注册点击事件
father.addEventListener('click',function(){
alert(' I am father!')
},true)
son.addEventListener('click',function(){
alert(' I am son!')
},true)
document.addEventListener('click',function(){
alert(' I am grandfather!')
},true)
</script>
</body>
</html>
运行结果:
(1).点击子级son:
先执行弹出爷爷级html文档document的内容,再执行弹出父级father的内容,最后弹出子级son的内容
(2).点击父级father:
弹出html文档document的内容,最后执行弹出本级father的内容
(3)html文档document:
弹出本级的内容
2.3.阻止事件流动
因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素。
若想把事件就限制在当前元素内,就需要阻止事件流动,阻止事件流动需要拿到事件对象
2.3.1.语法与实例
事件对象.stopPropagation()
此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效。
例:网页文档中有一个子级div的son 标签和它的父级div标签,
在子级son、父级father和爷爷级html文档document中都设置鼠标点击click事件,
点击子级son,观察阻止冒泡事件流动,
点击父级father和爷爷级html文档document,观察是否有子级事件发生。
代码:
<!DOCTYPE html>
<html lang="en">
<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>
.father{
margin: 100px auto;
width: 600px;
height: 600px;
background-color: red;
}
.son{
width: 100px;
height: 100px;
background-color:blue;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
// //阻止事件流动语法:
// 事件对象.stopPropagation()
//1.获取父级和子级标签
let father = document.querySelector('.father')
let son = document.querySelector('.son')
//2.给父级和子级都注册点击事件
father.addEventListener('click',function(e){
alert(' I am father!')
e.stopPropagation()
})
son.addEventListener('click',function(e){
alert(' I am son!')
e.stopPropagation()
})
document.addEventListener('click',function(){
alert(' I am grandfather!')
})
</script>
</body>
</html>
运行结果:
(1).点击子级son:
冒泡被阻止,只弹出子级son的内容,不弹出父级和爷爷级的内容
(2).点击父级father:
冒泡被阻止,只执行弹出本级father的内容,不弹出爷爷级的内容
2.3.2.鼠标经过事件的区别:
mouseover 和 mouseout 会有冒泡效果
mouseenter 和 mouseleave 没有冒泡效果(推荐)
例1:分别在父级father和子级son使用mouseover事件
<!DOCTYPE html>
<html lang="en">
<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>
.father{
margin: 100px auto;
width: 600px;
height: 600px;
background-color: red;
}
.son{
width: 100px;
height: 100px;
background-color:blue;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
// //阻止事件流动语法:
// 事件对象.stopPropagation()
//1.获取父级和子级标签
let father = document.querySelector('.father')
let son = document.querySelector('.son')
// 2.给父级和子级都注册点击事件
father.addEventListener('mouseover',function(e){
console.log(' I am father!')
})
son.addEventListener('mouseover',function(e){
console.log(' I am son!')
})
//father.addEventListener('mouseenter',function(e){
// console.log(' I am your father!')
//})
// son.addEventListener('mouseenter',function(e){
//console.log(' I am your son!')
//})
</script>
</body>
</html>
鼠标在父级和子级之间左右移动,运行结果如下:
例2:分别在父级father和子级son使用mouseenter事件
<!DOCTYPE html>
<html lang="en">
<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>
.father{
margin: 100px auto;
width: 600px;
height: 600px;
background-color: red;
}
.son{
width: 100px;
height: 100px;
background-color:blue;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
// //阻止事件流动语法:
// 事件对象.stopPropagation()
//1.获取父级和子级标签
let father = document.querySelector('.father')
let son = document.querySelector('.son')
// // 2.给父级和子级都注册点击事件
// father.addEventListener('mouseover',function(e){
// console.log(' I am father!')
// })
// son.addEventListener('mouseover',function(e){
// console.log(' I am son!')
// })
father.addEventListener('mouseenter',function(e){
console.log(' I am your father!')
})
son.addEventListener('mouseenter',function(e){
console.log(' I am your son!')
})
</script>
</body>
</html>
鼠标在父级和子级之间左右移动,运行结果如下:
2.3.3.阻止默认行为
阻止默认行为,比如链接点击不跳转,表单域的不提交
语法:
e.preventDefault()
例:阻止页面跳转到京东网
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body>
<a href="https://www.jd.com/">进入京东网</a>
<script>
//例:阻止页面跳转到京东网
let a = document.querySelector('a')
a.addEventListener('click',function(e){
//阻止默认行为语法:
e.preventDefault()
})
</script>
</body>
</html>
点击链接无法跳转
2.3.4.两种注册事件的区别:
1.传统on注册(L0)
同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
直接使用null覆盖偶就可以实现事件的解绑
都是冒泡阶段执行的
2.事件监听注册(L2)
语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
后面注册的事件不会覆盖前面注册的事件(同一个事件),可以通过第三个参数去确定是在冒泡或者捕获阶段执行,必须使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)
匿名函数无法被解绑
例:给按钮button设置两次点击事件click,第一次点击事件弹出‘第一次’,第二次点击事件弹出‘第二次’
(1)传统on注册(L0)
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
//1.L0 on
//多次相同的事件,只执行最后一次
btn.onclick = function (){
alert('第一次')
}
btn.onclick = function (){
alert('第二次')
}
</script>
</body>
</html>
只弹出第二次,不弹出第一次
解绑事件:不弹出任何东西
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
//1.L0 on
//多次相同的事件,只执行最后一次
btn.onclick = function (){
alert('第一次')
}
btn.onclick = function (){
alert('第二次')
}
//解绑事件
btn.onclick = null
</script>
</body>
</html>
2.事件监听注册(L2)
语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
btn.addEventListener('click',add)
function add (){
alert('第一次')
}
btn.addEventListener('click',function(){
alert('第二次')
})
</script>
</body>
</html>
第一次和第二次弹窗均会弹出
使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)解绑弹出“第一次”
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
btn.addEventListener('click',add)
function add (){
alert('第一次')
}
btn.addEventListener('click',function(){
alert('第二次')
})
//使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)解绑弹出“第一次”
btn.removeEventListener('click',add)
</script>
</body>
</html>
只弹出第二次
3.事件委托
事件委托是利用事件流的特征解决一些开发需求的知识技巧
优点
给父级元素加事件(可以提高性能)
原理
事件委托其实是利用事件冒泡的特点, 给父元素添加事件,子元素可以触发
实现:
事件对象.target 可以获得真正触发事件的元素
案例: 点击li中的文字让任意被点击的文字变红色
思路:
1.通过事件委托,绑定事件给父级,
2.事件对象.target 获得真正触发事件的元素
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body>
<ul>
<li>I am Number 1</li>
<li>I am Number 2</li>
<li>I am Number 3</li>
<li>I am Number 4</li>
<li>I am Number 5</li>
</ul>
<script>
//例:点击li中的文字让任意被点击的文字变红色
//思路:通过事件委托,绑定事件给父级,事件对象.target 获得真正触发事件的元素
// 注意:
//1.不要给每个li注册事件 而是把事件委托给父级
//2.事件委托是给父级添加事件 而不是孩子添加事件
let ul = document.querySelector('ul')
ul.addEventListener('click',function(e){
//观察是否得到当前的元素
console.log(e.target)
e.target.style.color = 'red'
})
</script>
</body>
</html>
4.综合案例-渲染学生信息案例
需求:点击录入按钮,可以增加学生信息
4.1.素材
HTML与JavaScript部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="css/user.css">
</head>
<body>
<h1>新增学员</h1>
<div class="info">
姓名:<input type="text" class="uname">
年龄:<input type="text" class="age">
性别: <select name="gender" id="" class="gender">
<option value="男">男</option>
<option value="女">女</option>
</select>
学号:<input type="text" class="stuId">
薪资:<input type="text" class="salary">
就业城市:<select name="city" id="" class="city">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
<option value="深圳">深圳</option>
<option value="曹县">曹县</option>
</select>
<button class="add">录入</button>
</div>
<h1>就业榜</h1>
<table>
<thead>
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>薪资</th>
<th>就业城市</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1001</td>
<td>欧阳霸天</td>
<td>19</td>
<td>男</td>
<td>15000</td>
<td>上海</td>
<td>
<a href="javascript:">删除</a>
</td>
</tr>
</tbody>
</table>
<script>
// 1. 准备好数据后端的数据
let arr = [
{ stuId: 1001, uname: '欧阳霸天', age: 19, gender: '男', salary: '20000', city: '上海' },
{ stuId: 1002, uname: '令狐霸天', age: 29, gender: '男', salary: '30000', city: '北京' },
{ stuId: 1003, uname: '诸葛霸天', age: 39, gender: '男', salary: '2000', city: '北京' },
]
</script>
</body>
</html>
CSS
* {
margin: 0;
padding: 0;
}
a {
text-decoration: none;
color:#721c24;
}
h1 {
text-align: center;
color:#333;
margin: 20px 0;
}
table {
margin:0 auto;
width: 800px;
border-collapse: collapse;
color:#004085;
}
th {
padding: 10px;
background: #cfe5ff;
font-size: 20px;
font-weight: 400;
}
td,th {
border:1px solid #b8daff;
}
td {
padding:10px;
color:#666;
text-align: center;
font-size: 16px;
}
tbody tr {
background: #fff;
}
tbody tr:hover {
background: #e1ecf8;
}
.info {
width: 900px;
margin: 50px auto;
text-align: center;
}
.info input {
width: 80px;
height: 25px;
outline: none;
border-radius: 5px;
border:1px solid #b8daff;
padding-left: 5px;
}
.info button {
width: 60px;
height: 25px;
background-color: #004085;
outline: none;
border: 0;
color: #fff;
cursor: pointer;
border-radius: 5px;
}
.info .age {
width: 50px;
}
4.2.需求
需求1:渲染
把数组的数据渲染到页面中,同时清空表单里面的值,下拉列表的值复原
注意,渲染之前,先清空以前渲染的内容
因为多次渲染,最好封装为函数
需求2:添加数据
点击录入按钮,把表单里面的值都放入数组里面
学号自动生成,是数组最后一个数据的学号+1
需求3:删除数据:只能点击了链接,才能执行删除操作
4.3.需求实现
4.3.1.需求1:渲染数组内的数据
代码与思路:
//需求1:渲染
// 把数组的数据渲染到页面中,同时清空表单里面的值,下拉列表的值复原
// 注意,渲染之前,先清空以前渲染的内容
// 因为多次渲染,最好封装为函数
//1.获取父级标签,创建节点
let tbody = document.querySelector('tbody')
function dataRender(){
for (let i = 0; i < arr.length ; i++){
//2.循环创建和增加子级节点
let tr = document.createElement('tr')
tbody.appendChild(tr)
// 3.每当创建和增加一个子级节点,就循环将tr里面的数据更换为数组arr里面的数据
for( let j = 0 ; j < arr.length;j++){
tr.innerHTML =
`<tr>
<td>${arr[i].stuId}</td>
<td>${arr[i].uname}</td>
<td>${arr[i].age}</td>
<td> ${arr[i].gender}</td>
<td>${arr[i].salary}</td>
<td>${arr[i].city}</td>
<td>
<a href="javascript:">删除</a>
</td>
</tr> `
}
}
//观察父级标签和子级节点是否创建成功
console.log(tbody)
}
//4.调用和执行渲染数据函数
dataRender()
4.3.2.需求2:添加数据
代码与思路:
// 需求2:添加数据
// 点击录入按钮,把表单里面的值都放入数组里面
// 学号自动生成,是数组最后一个数据的学号+1
//事件:表单输入数据后,点击录入按钮,表单里面的数据放入数组
//事件源:点击录入按钮
//1.获取录入按钮和有关操作的input输入标签
let add = document.querySelector('.add')
let uname = document.querySelector('.uname')
let age = document.querySelector('.age')
let gender = document.querySelector('.gender')
let stuId = document.querySelector('.stuId')
let salary = document.querySelector('.salary')
let city = document.querySelector('.city')
//2.表单输入数据后,点击录入按钮,表单里面的数据放入数组
add.addEventListener('click',function(){
//检测录入事件是否成功
//alert(123)
//2.1.渲染之前,先清空以前渲染的内容
tbody.innerHTML = ''
//2.2.向数组里面追加数据
arr.push( { stuId:`${parseInt(arr[arr.length-1].stuId)+1}` ,
uname: `${uname.value}`,
age: `${age.value}`,
gender: `${gender.value}`,
salary: `${salary.value}`,
city: `${city.value}`
},)
//测试观察arr是否追加成功
//console.log(arr)
//2.3.调用渲染函数,将追加数组后的数组渲染到页面中去
dataRender()
//2.4.渲染结束后恢复为表单追加前的默认数据,学号、姓名、年龄、薪水为空,性别默认为男,城市默认为北京
stuId.value = uname.value = age.value = salary.value = ''
gender.value = '男'
city.value = '北京'
})
4.3.3.需求3:删除数据:只能点击删除文字,才能执行删除操作
自己不会实现,没有思路。。。。。。
参考思路
1.为了提高性能,最好使用事件委托方式,找到点击的是链接 e.target.tagName,
2.根据当前的删除链接,找到这条数据,
3.需要得到当前数据的索引号,可以渲染a的时候,把当前索引号给 id属性,然后通 过 e.target.id 来获取,
4.然后使用 splice 来删除对应数据
5.重新渲染
参考代码
// 删除操作, 删除的也是数组里面的数据 , 但是我们用事件委托
tbody.addEventListener('click', function (e) {
// alert(11)
// 我们只能点击了链接 a ,才会执行删除操作
// 那我们怎么知道你点击了a呢?
// 俺们只能点击了链接才能做删除操作
// console.dir(e.target.tagName)
if (e.target.tagName === 'A') {
// alert('你点击了链接')
// 删除操作 删除 数组里面的数据 arr.splice(从哪里开始删,1)
// 我要得到a的id 需要
// console.log(e.target.id)
arr.splice(e.target.id, 1)
// 重新渲染我们的函数
render()
}
})
参考代码自我梳理与代码完善
//需求3:删除数据:只能点击了链接,才能执行删除操作
// 参考思路:
// 1.为了提高性能,最好使用事件委托方式,找到点击的是链接 e.target.tagName
// 2.根据当前的删除链接,找到这条数据
// 3.需要得到当前数据的索引号,可以渲染a的时候,把当前索引号给 id属性,然后通 过 e.target.id 来获取
// 4.然后使用 splice 来删除对应数据
// 5.重新渲染
//详细思路:
//使用事件委托,父级事件监听,事件对象.target
//只能点击每行数据tr父级tbody中删除标签A,tr标签内的数据被删除
tbody.addEventListener('click',function(e){
//测试:事件监听:点击tbody的任意地方tbody内均有11弹出即可确定实现事件监听
// alert(11)
//我们只能点击了链接a ,才会执行删除操作
//那我们怎么知道你点击了a呢?
//需求是我们只有点击了链接才能执行删除操作
//console.dir(e.target.tagName) //测试观察到你点击的是链接A标签
//1.事件对象,事件委托判断点击的是删除A标签
if(e.target.tagName === 'A'){
//测试观察点的是链接
//alert('你好!')
//2.删除操作 删除 数组里面的数据 arr.splice(从哪里开始删,删几个)
//在渲染数据时给链接A设置与在渲染数据时对应数组索引号对应的id
//怎样确定从哪开始删?
//找到要删除数据中删除标签a链接对应的id ,从这个第id个开始删
// console.log(e.target.id)//测试:点击删除标签a,观察对应的id
arr.splice(e.target.id,1)
//3.清空删除之前渲染的数据
tbody.innerHTML = ''
//4.重新渲染数据
dataRender()
}
})
录入以下数据
点击删除链接删除学号1004的数据,结果如下:
4.4.完整代码
<script>
// 1. 准备好数据后端的数据
let arr = [
{ stuId: 1001, uname: '欧阳霸天', age: 19, gender: '男', salary: '20000', city: '上海' },
{ stuId: 1002, uname: '令狐霸天', age: 29, gender: '男', salary: '30000', city: '北京' },
{ stuId: 1003, uname: '诸葛霸天', age: 39, gender: '男', salary: '2000', city: '北京' },
]
//需求1:渲染
// 把数组的数据渲染到页面中,同时清空表单里面的值,下拉列表的值复原
// 注意,渲染之前,先清空以前渲染的内容
// 因为多次渲染,最好封装为函数
//1.获取父级标签,创建节点
let tbody = document.querySelector('tbody')
function dataRender(){
for (let i = 0; i < arr.length ; i++){
//2.循环创建和增加子级节点
let tr = document.createElement('tr')
tbody.appendChild(tr)
// 3.每当创建和增加一个子级节点,就循环将tr里面的数据更换为数组arr里面的数据
for( let j = 0 ; j < arr.length;j++){
tr.innerHTML =
`<tr>
<td>${arr[i].stuId}</td>
<td>${arr[i].uname}</td>
<td>${arr[i].age}</td>
<td> ${arr[i].gender}</td>
<td>${arr[i].salary}</td>
<td>${arr[i].city}</td>
<td>
<a href="javascript:" id = "${i}">删除</a>
</td>
</tr> `
}
}
//观察父级标签和子级节点是否创建成功
//console.log(tbody)
}
//4.调用和执行渲染数据函数
dataRender()
// 需求2:添加数据
// 点击录入按钮,把表单里面的值都放入数组里面
// 学号自动生成,是数组最后一个数据的学号+1
//事件:表单输入数据后,点击录入按钮,表单里面的数据放入数组
//事件源:点击录入按钮
//1.获取录入按钮和有关操作的input输入标签
let add = document.querySelector('.add')
let uname = document.querySelector('.uname')
let age = document.querySelector('.age')
let gender = document.querySelector('.gender')
let stuId = document.querySelector('.stuId')
let salary = document.querySelector('.salary')
let city = document.querySelector('.city')
//2.表单输入数据后,点击录入按钮,表单里面的数据放入数组
add.addEventListener('click',function(){
//检测录入事件是否成功
//alert(123)
//2.1.渲染之前,先清空以前渲染的内容
tbody.innerHTML = ''
//2.2.向数组里面追加数据
arr.push( { stuId:`${parseInt(arr[arr.length-1].stuId)+1}` ,
uname: `${uname.value}`,
age: `${age.value}`,
gender: `${gender.value}`,
salary: `${salary.value}`,
city: `${city.value}`
},)
//测试观察arr是否追加成功
//console.log(arr)
//2.3.调用渲染函数,将追加数组后的数组渲染到页面中去
dataRender()
//2.4.渲染结束后恢复为表单追加前的默认数据,学号、姓名、年龄、薪水为空,性别默认为男,城市默认为北京
stuId.value = uname.value = age.value = salary.value = ''
gender.value = '男'
city.value = '北京'
})
//需求3:删除数据:只能点击了链接,才能执行删除操作
// 参考思路:
// 1.为了提高性能,最好使用事件委托方式,找到点击的是链接 e.target.tagName
// 2.根据当前的删除链接,找到这条数据
// 3.需要得到当前数据的索引号,可以渲染a的时候,把当前索引号给 id属性,然后通 过 e.target.id 来获取
// 4.然后使用 splice 来删除对应数据
// 5.重新渲染
//详细思路:
//使用事件委托,父级事件监听,事件对象.target
//只能点击每行数据tr父级tbody中删除标签A,tr标签内的数据被删除
tbody.addEventListener('click',function(e){
//测试:事件监听:点击tbody的任意地方tbody内均有11弹出即可确定实现事件监听
// alert(11)
//我们只能点击了链接a ,才会执行删除操作
//那我们怎么知道你点击了a呢?
//需求是我们只有点击了链接才能执行删除操作
//console.dir(e.target.tagName) //测试观察到你点击的是链接A标签
//1.事件对象,事件委托判断点击的是删除A标签
if(e.target.tagName === 'A'){
//测试观察点的是链接
//alert('你好!')
//2.删除操作 删除 数组里面的数据 arr.splice(从哪里开始删,删几个)
//在渲染数据时给链接A设置与在渲染数据时对应数组索引号对应的id
//怎样确定从哪开始删?
//找到要删除数据中删除标签a链接对应的id ,从这个第id个开始删
// console.log(e.target.id)//测试:点击删除标签a,观察对应的id
arr.splice(e.target.id,1)
//3.清空删除之前渲染的数据
tbody.innerHTML = ''
//4.重新渲染数据
dataRender()
}
})
</script>