事件对象
获取事件对象
●事件对象是什么
也是个对象,这个对象里有事件触发时的相关信息 例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息
●如何获取
在事件绑定的回调函数的第一个参数就是事件对象 一般命名为event、ev、e
语法:
<script>
元素.addEventListener('click',function(e事件对象){
})
</script>
事件对象常用属性
●部分常用属性
type:获取当前的事件类型
clientX/clientY:获取光标相对于浏览器可见窗口左上角的位置
offsetX/offsetY:获取光标相对于当前DOM元素左上角的位置
key:用户按下的键盘键的值,现在不提倡使用keyCode
●鼠标跟随案例
分析: ①:鼠标在页面中移动,用到 mousemove 事件 ②:不断把鼠标在页面中的坐标位置给图片left和top值即可
代码:
<body>
<img src="./images/tianshi.gif" alt="" />
<script>
let img = document.querySelector('img')
document.addEventListener('mousemove', function (e) {
console.log(e.clientX, e.clientY)
// 将鼠标的相对于视口的坐标位置赋值给img标签的top和left(图片高度的一半)
img.style.left = e.clientX - img.offsetWidth / 2 + 'px'
img.style.top = e.clientY - img.offsetHeight / 2+ 'px'
})
</script>
</body>
●按下回车键发布微博案例
分析: ①:用到按下键盘事件 keydown 或者 keyup 都可以 ②:如果用户按下的是回车键盘,则发布信息 ③:按下键盘发布新闻,其实和点击发布按钮效果一致 send.click()
代码:
<script>
//(以上内容省略,在DOM节点操作查看)
// 为输入框添加按键事件,当按下的键是enter键时,实现发布内容
area.addEventListener('keyup',function(){
// 判断是否按下了enter键
if(e.key==='Enter'){
// 实现发布功能
send.click()
}
})
</script>
事件流
事件流与两个阶段说明
●事件流指的是事件完整执行过程中的流动路径
说明:假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段
简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父
事件捕获和事件冒泡
●事件冒泡概念:
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡
●简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件
●事件冒泡是默认存在的
代码:
<script>
let father = document.querySelector('.father')
let son = document.querySelector('.son')
father.addEventListener('click', function () {
console.log('father'),
true
})
son.addEventListener('click', function () {
console.log('son'),
true
})
document.body.addEventListener('click', function () {
console.log('body'),
true
})
</script>
●事件捕获概念:
从DOM的根元素开始去执行对应的事件 (从外到里)
●事件捕获需要写对应代码才能看到效果
●代码:
<script>
DOM.addEventListener(事件类型,事件处理函数,是否使用捕获机制)
</script>
●说明:
1、addEventListener第三个参数传入true代表是捕获阶段触发(很少使用)
2、若传入false代表冒泡阶段触发,默认就是false
3、若是用 L0 事件监听,则只有冒泡阶段,没有捕获
阻止事件流动
●因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素
●若想把事件就限制在当前元素内,就需要阻止事件流动
●阻止事件流动需要拿到事件对象
语法:
<script>
事件对象.stopPropagation()
</script>
●此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效
代码:
<script>
let father = document.querySelector('.father')
let son = document.querySelector('.son')
// 事件捕获
father.addEventListener('click', function () {
console.log('father')
})
son.addEventListener('click', function (e) {
// 阻止事件冒泡
e.stopPropagation()
console.log('son')
})
document.body.addEventListener('click', function () {
console.log('body')
})
</script>
mouseover和mouseenter区别
●鼠标经过事件:
mouseover 和 mouseout 会有冒泡效果
mouseenter 和 mouseleave 没有冒泡效果(推荐)
代码:
<script>
let father = document.querySelector('.father')
let son = document.querySelector('.son')
// 事件捕获
father.addEventListener('mouseover', function() {
console.log('father-mouseover')
})
father.addEventListener('mouseout', function() {
console.log('father-mouseout')
})
son.addEventListener('mouseover', function(e) {
console.log('son-mouseover')
})
son.addEventListener('mouseout', function(e) {
console.log('son-mouseout')
})
</script>
阻止默认行为
比如链接点击不跳转,表单域的不提交
语法:
<script>
e.preventDefault()
</script>
代码:
<body>
<a href="http://www.baidu.com">跳转到百度</a>
<script>
let a = document.querySelector('a')
a.addEventListener('click', function(e) {
// 阻止元素的默认行为
e.preventDefault()
console.log(123)
})
</script>
</body>
事件的解绑
●两种注册事件的区别:
传统on注册(L0)
1、同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
2、直接使用null覆盖偶就可以实现事件的解绑
3、都是冒泡阶段执行的
●事件监听注册(L2)
1、语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
2、后面注册的事件不会覆盖前面注册的事件(同一个事件)
3、可以通过第三个参数去确定是在冒泡或者捕获阶段执行
4、必须使用removeEventListener(事件类型, 事件处理函数, 获取捕 获或者冒泡阶段)
5、匿名函数无法被解绑
代码:
<body>
<button>按钮</button>
<script>
let btn = document.querySelector('button')
// btn.onclick = function() {
// console.log(123)
// btn.onclick = null
// }
function deal() {
console.log(123)
btn.removeEventListener('click', deal)
}
btn.addEventListener('click', deal)
</script>
</body>
事件委托
●事件委托是利用事件流的特征解决一些开发需求的知识技巧
●总结: 1、优点:给父级元素加事件(可以提高性能)
2、原理:事件委托其实是利用事件冒泡的特点, 给父元素添加事件, 子元素可以触发
3、实现:事件对象.target 可以获得真正触发事件的元素
代码:
<body>
<button>添加li元素</button>
<ul>
<li class="a">我是第1个小li</li>
<li class="a">我是第2个小li</li>
<li class="a">我是第3个小li</li>
<li class="a">我是第4个小li</li>
<li class="a">我是第5个小li</li>
</ul>
<script>
// 需求:单击li元素,为其添加背景色
// 这里获取元素的时候,只能获取到页面已存在的li元素,对于未来的li无法获取到
let lis = document.querySelectorAll('li')
let button = document.querySelector('button')
let ul = document.querySelector('ul')
button.addEventListener('click', function() {
let newLi = document.createElement('li')
newLi.innerText = '我是新的li元素'
newLi.className = 'a'
ul.appendChild(newLi)
})
// 事件捕获:单击ul区域,它会传递给子元素进行响应
// 子响应响应事件之后会将事件冒泡给父元素,如果父元素有同名事件,那么就会触发
document.body.addEventListener('click', function(e) {
// e.target:真正触发事件的元素,说白了,就是你当前单击的元素
console.dir(e.srcElement.localName)
// 判断当前触发事件的元素是否是我想要的元素
if (e.target.className === 'a') {
e.target.style.background = 'red'
}
})
</script>
</body>
综合案例
●渲染学生信息案例
需求:点击录入按钮,可以增加学生信息
●分析:
不管添加还是删除,都是操作的数据(数组),然后从新渲染页面
需求①:添加数据 1、点击录入按钮,把表单里面的值都放入数组里面 2、学号自动生成,是数组最后一个数据的学号+1
需求②:渲染 1、把数组的数据渲染到页面中,同时清空表单里面的值,下拉列 表的值复原 2、注意,渲染之前,先清空以前渲染的内容,因为多次渲染,最 好封装为函数
需求③:删除数据 1、为了提高性能,最好使用事件委托方式,找到点击的是链 e.target.tagName
2、根据当前的删除链接,找到这条数据 3、需要得到当前数据的索引号,可以渲染a的时候,把当前索引 号给 id属性,然后通过 e.target.id 来获取,然后使用 splice来删 除对应数据,重新渲染
代码:
<!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="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></tbody>
</table>
<script>
// 0.获取元素
let tbody = document.querySelector('tbody')
let add = document.querySelector('.add')
// 获取几个表单元素
let uname = document.querySelector('.uname')
let age = document.querySelector('.age')
let gender = document.querySelector('.gender')
let salary = document.querySelector('.salary')
let city = document.querySelector('.city')
// 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.实现数据的动态渲染
function init() {
// 1.1 定义一个就是用于拼接html结构
let htmlStr = ''
// 1.2 遍历数组,生成动态结构
arr.forEach(function(value, i) {
htmlStr += `<tr>
<td>${value.stuId}</td>
<td>${value.uname}</td>
<td>${value.age}</td>
<td>${value.gender}</td>
<td>${value.salary}</td>
<td>${value.city}</td>
<td>
<a href="javascript:" class='del' id='${i}'>删除</a>
</td>
</tr>`
})
// 1.3 将生成的动态结构填充到指定的标签中
tbody.innerHTML = htmlStr
}
init()
// 2.实现数据的添加
add.addEventListener('click', function() {
// 用户输入的验证
// if (uname.value.trim().length == 0) {
// alert('请输入用户名')
// uname.focus()
// return
// }
// if (age.value.trim().length == 0) {
// alert('请输入年龄')
// age.focus()
// return
// }
// if (salary.value.trim().length == 0) {
// alert('请输入薪资')
// salary.focus()
// return
// }
// 2.1 收集用户数据,生成一个对象
let obj = {
// 如果数据有成员,就取最后一个元素的id+1,否则默认设置为1001
stuId: arr.length > 0 ? arr[arr.length - 1].stuId + 1 : 1001,
uname: uname.value,
age: age.value,
gender: gender.value,
salary: salary.value,
city: city.value
}
// 2.2 将生成的对象添加到数组
arr.push(obj)
// 2.3 重新渲染
init()
})
// 3.委托方式绑定事件
tbody.addEventListener('click', function(e) {
if (e.target.className == 'del') {
// 说明单击了删除
// 删除数组中的某个元素
arr.splice(e.target.id, 1)
// 重新渲染
init()
}
})
</script>
</body>
</html>
●购物车案例
需求:渲染数据,数量增减,删除
<!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;
}
a {
text-decoration: none;
color: #666;
}
body {
background: #fff;
color: #666;
font-size: 14px;
}
input {
outline: none;
}
.clearfix::before,
.clearfix::after {
content: '';
display: block;
clear: both;
}
.clearfix {
*zoom: 1;
}
</style>
<!-- 引入购物车样式 -->
<style>
table {
width: 800px;
margin: 0 auto;
border-collapse: collapse;
}
th {
font: normal 14px/50px '宋体';
color: #666;
}
th,
td {
border: none;
text-align: center;
border-bottom: 1px dashed #ccc;
}
input[type='checkbox'] {
width: 13px;
height: 13px;
}
tbody p {
position: relative;
bottom: 10px;
}
tbody .add,
tbody .reduce {
float: left;
width: 22px;
height: 22px;
border: 1px solid #ccc;
text-align: center;
background: none;
outline: none;
cursor: pointer;
}
tbody input[type='text'] {
width: 50px;
float: left;
height: 18px;
text-align: center;
}
tbody .count-c {
width: 98px;
margin: 0 auto;
}
button[disabled] {
color: #ddd;
cursor: not-allowed;
}
tbody tr:hover {
background: #eee;
}
tbody tr.active {
background: rgba(241, 209, 149, 0.945);
}
.controls {
width: 790px;
margin: 10px auto;
border: 1px solid #ccc;
line-height: 50px;
padding-left: 10px;
position: relative;
}
.controls .del-all,
.controls .clear {
float: left;
margin-right: 50px;
}
.controls p {
float: right;
margin-right: 100px;
}
.controls span {
color: red;
}
.controls .pay {
position: absolute;
right: 0;
width: 80px;
height: 54px;
background: red;
font: bold 20px/54px '宋体';
color: #fff;
text-align: center;
bottom: -1px;
}
.controls .total-price {
font-weight: bold;
}
</style>
</head>
<body>
<div class="car">
<table>
<thead>
<tr>
<th><input type="checkbox" id="all" />全选</th>
<th>商品</th>
<th>单价</th>
<th>商品数量</th>
<th>小计</th>
<th>操作</th>
</tr>
</thead>
<tbody id="carBody"></tbody>
</table>
<div class="controls clearfix">
<a href="javascript:" class="del-all">删除所选商品</a>
<a href="javascript:" class="clear">清理购物车</a>
<a href="javascript:" class="pay">去结算</a>
<p>
已经选中<span id="totalCount">0</span>件商品;总价:<span
id="totalPrice"
class="total-price"
>0¥</span
>
</p>
</div>
</div>
<script>
// 0 获取元素
let carBody = document.querySelector('#carBody')
// 1.模拟数据
let datas = [
{
id: 1, //任何数据都会有id号
state: true,
img: './images/01.jpg',
name: '牛奶',
price: 5,
count: 2
},
{
id: 2,
state: false,
img: './images/01.jpg',
name: '奶牛',
price: 10,
count: 5
},
{
id: 3,
state: true,
img: './images/01.jpg',
name: '酸奶',
price: 3,
count: 1
}
]
// 2.动态渲染:数据 + 静态结构 遍历拼接
function init() {
let htmlStr = ''
datas.forEach(function(value, index) {
htmlStr += `<tr>
<td>
<input class="s_ck" type="checkbox" ${
value.state ? 'checked' : ''
} />
</td>
<td>
<img src="${value.img}" />
<p>${value.name}</p>
</td>
<td class="price">${value.price}¥</td>
<td>
<div class="count-c clearfix">
<button class="reduce" disabled>-</button>
<input type="text" value="${value.count}" />
<button class="add" id=${index}>+</button>
</div>
</td>
<td class="total">${value.price * value.count}¥</td>
<td>
<a href="javascript:" class="del">删除</a>
</td>
</tr>`
})
carBody.innerHTML = htmlStr
}
init()
// 数量增加
carBody.addEventListener('click', function(e) {
if (e.target.className == 'add') {
let pre = e.target.previousElementSibling
// pre.value = (+pre.value) + 1
datas[e.target.id].count++
init()
} else if (e.target.className == 'reduce') {
console.log('----')
}
})
</script>
</body>
</html>