一、注册事件
给元素添加事件称为注册事件或绑定事件,有两种方式:传统注册方式与监听注册方式
1-1、传统注册方式
- 利用on开头的事件
- 注册事件有唯一性(同一个元素同一个事件只能设置同一个处理函数,最后注册的处理函数会覆盖前面注册的处理函数)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
height: 200px;
width: 200px;
background: salmon;
}
p {
width: 100px;
height: 100px;
background: springgreen;
}
</style>
</head>
<body>
<div>
<p></p>
</div>
<script>
var div = document.querySelector('div');
// 当点击div时,只会输出2
// 事件绑定 下面的会覆盖上面的
div.onclick = function() {
console.log(1);
}
div.onclick = function() {
console.log(2);
}
</script>
</body>
</html>
1-2、监听注册方式
- W3C标准,推荐方式
- 使用addEventListener()方法(IE9之前不支持,可使用attchEvent()代替)
- 同一个元素同一个事件可以注册多个监听器,按注册顺序依次执行:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
height: 200px;
width: 200px;
background: salmon;
}
p {
width: 100px;
height: 100px;
background: springgreen;
}
</style>
</head>
<body>
<div>
<p></p>
</div>
<script>
var div = document.querySelector('div');
// 通过addEventListener绑定事件不会覆盖,会依次执行,依次输出1 2
div.addEventListener('click', function(){
console.log(1);
});
div.addEventListener('click', function() {
console.log(2);
});
</script>
</body>
</html>
执行事件流有两种方式:事件冒泡(默认方式) 、事件捕获
语法:addEventListener(type, listener[, options|useCapture])
- type: 事件名(不需要加on)
- listener:执行函数
- 第三个参数可以是布尔值,也可以是一个对象:
- options:是一个对象,表示事件监听相关配置,包含以下属性:
- capture:是否再捕获阶段执行(true/false)
- once:是否只执行一次(true/false)
- passive:阻止取消默认事件(true/ false): true不阻止默认事件,false阻止默认事件
- useCapture:是否捕获执行(布尔值):true捕获执行,false不捕获执行也就是冒泡执行
- true:开启捕获执行,执行顺序由大到小 如body > div > p(要给对应元素的对应事件都加上true才行,不然不会按照期望的捕获方式去执行)
- false:不捕获执行,也就是冒泡执行,冒泡的执行顺序,由小到大:p > div > body
- options:是一个对象,表示事件监听相关配置,包含以下属性:
1-2-1、捕获执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
height: 200px;
width: 200px;
background: salmon;
}
p {
width: 100px;
height: 100px;
background: springgreen;
}
</style>
</head>
<body>
<div>
<p></p>
</div>
<script>
var div = document.querySelector('div');
var p = document.querySelector('p');
// 开启捕获执行,点击p标签时,依次会输出 body div p
// 注意!要为每个元素的对应事件都要开启捕获,不然不会按照预期执行
document.body.addEventListener('click', function() {
console.log('body');
}, true);
div.addEventListener('click', function() {
console.log('div');
},true);
p.addEventListener('click', function() {
console.log('p');
},true);
// 或这样写:
// document.body.addEventListener('click', function() {
// console.log('body');
// }, {
// capture: true
// });
// div.addEventListener('click', function() {
// console.log('div');
// },{
// capture: true
// });
// p.addEventListener('click', function() {
// console.log('p');
// },{
// capture: true
// });
</script>
</body>
</html>
1-2-2、冒泡执行
// 冒泡执行,点击p时,依次输出p div
// 再次点击,只输出p,因为div开启了只执行一次
div.addEventListener('click', function() {
console.log('div');
},{
once: true
});
p.addEventListener('click', function() {
console.log('p');
});
二、删除事件
- 删除传统注册方式事件
如:div.onclick = null
- 删除监听注册方式事件
removeEventListener(事件名,要移除的绑定的函数)
- 要注意,要保证保定函数与要移除的函数是同一个
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 100px;
height: 100px;
background: springgreen;
}
</style>
</head>
<body>
<div></div>
<input type="button" value="取消事件">
<script>
var div = document.querySelector('div');
var inp = document.querySelector('input');
function fn() {
console.log('111');
}
div.addEventListener('click', fn);
inp.addEventListener('click', function() {
div.removeEventListener('click', fn);
});
</script>
</body>
</html>
三、DOM事件流
事件流描述的是从页面中接收事件的顺序。
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流
DOM事件流分为3个阶段:
- 捕获阶段
- 当前目标阶段
- 冒泡阶段
比如,给div绑定了一个点击事件:
注意:
- JS代码中只能执行捕获或冒泡其中的一个阶段
- onclick只能得到冒泡阶段
- addEventListener的第三个参数如果是true,表示事件捕获阶段调用事件处理程序;如果是false,表示事件冒泡阶段调用事件处理程序
- 有些事件是没有冒泡的,如:onblur、onfocus、onmouseenter、onmouseleave
四、事件对象
事件对象event中包含了事件发生后,跟该事件相关的一系列信息数据的集合
事件对象的常见属性和方法:
事件对象属性与方法 | 说明 |
---|---|
e.target | 返回触发事件的对象 |
e.currentTarget | 事件绑定的元素 |
e.srcElement | 返回触发事件的对象(非标准,ie6-8) |
e.type | 返回事件的类型(如:click mouseover),不带on |
e.cancelBubble | 阻止事件冒泡(非标准,ie6-8) |
e.returnValue | 阻止默认事件(非标准,ie6-8) |
e.preventDefault() | 阻止默认事件,标准 |
e.stopPropagation() | 阻止事件冒泡,标准 |
- 事件对象本身的获取存在兼容问题:
-
- 在标准浏览器中给方法传递的参数,只需要定义形参e就可以获取到
- 在IE6~8中,浏览器不会给发给发传递参数,需要到window.event中获取
解决: e = e || window.event;
-
e.target & e.currentTarget
- target:事件触发的目标源元素
- currentTarget: 事件绑定的元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
height: 200px;
width: 200px;
background: salmon;
margin: 100px auto;
}
p {
width: 100px;
height: 100px;
background: springgreen;
}
</style>
</head>
<body>
<div>
<p></p>
</div>
<script>
{
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', function(e) {
// 点击p标签所在的区域会打印出<p></p>及其中的内容
// 点击p标签之外的区域会打印出<div></div>及其中的内容
console.log(e.target);
// 点击p标签之外的区域会打印出<div></div>及其中的内容
// 点击p标签所在的区域也还会会打印出<div></div>及其中的内容
console.log(e.currentTarget);
})
}
</script>
</body>
</html>
五、阻止事件冒泡
- 标准写法:e.stopPropagation()
- 非标准写法(ie6-8): e.cancelBubble = true;
阻止事件冒泡的兼容解决方案:
if(e && e.stopPropagation){
e.stopPropagation();
}else{
window.event.cancelBubble = true;
}
六、事件委托(代理、委派)
利用事件冒泡的特性,为一个元素添加了某个事件,相当于其子元素也会触发相应的事件
-
优点:
- 可减少需要添加事件绑定的元素
- 可给新增DOM元素添加事件
- 只操作一次DOM,提高性能
如下面的例子,不是每个子节点都要单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置的每个子节点,点击li也会触发ul的click,只给ul添加click即可:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
li {
width: 100px;
height: 100px;
background: springgreen;
margin: 50px auto;
}
p {
width: 50px;
height: 50px;
background: tomato;
}
</style>
</head>
<body>
<div>
<ul>
<li>
<p></p>
</li>
<li>
<p></p>
</li>
<li>
<p></p>
</li>
<li>
<p></p>
</li>
<li>
<p></p>
</li>
</ul>
</div>
<script>
{
var ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
e.target.style.background = 'yellow';
}
});
}
</script>
</body>
</html>
七、常用的鼠标事件
7-1、常用鼠标事件
鼠标事件 | 触发条件 |
---|---|
onclick | 鼠标点击左键触发 |
onmouseover | 鼠标经过触发 |
onmouseout | 鼠标离开触发 |
onmouseenter | 鼠标移入时触发 |
onmouseleave | 鼠标移出时触发 |
onfocus | 获得鼠标焦点触发 |
onblur | 失去鼠标焦点触发 |
onmousemove | 鼠标移动触发 |
onmouseup | 鼠标按键弹起触发 |
onmousedown | 鼠标按下触发 |
oncontextmenu | 鼠标右击触发 |
onselectstart | 鼠标选中触发 |
7-1-1、对比mouseover mouseout与mouseenter mouseleave
- mouseover与mouseout会受子元素影响:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div
{
width: 200px;
height: 200px;
background: tomato;
margin: 100px auto;
}
p
{
width: 100px;
height: 100px;
background: springgreen;
margin: 50px;
}
</style>
</head>
<body>
<div>
<p></p>
</div>
<script>
const div = document.querySelector('div');
/*
当移入div时会打印出:移入了
当移入div中子元素p时会同时打印出: 移出了移入了
当移出div中子元素p但还未移出div时会同时打印出:移出了移入了
当移出div时会打印出:移出了
即: 如果鼠标移入和移出子元素的范围也会触发:mouseover和mouseout
*/
div.addEventListener('mouseover', function() {
console.log('移入了');
});
div.addEventListener('mouseout', function() {
console.log('移出了');
})
</script>
</body>
</html>
- mouseenter与mouseleave不会受子元素影响:
/*
mouseenter 和 mouseleave 不受子元素范围干扰
*/
div.addEventListener('mouseenter', function() {
console.log('移入了');
});
div.addEventListener('mouseleave', function() {
console.log('移出了');
})
7-1-2、禁止鼠标右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
7-1-3、禁止鼠标选中
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
7-2、鼠标事件对象
鼠标事件对象 | 说明 |
---|---|
e.clientX | 返回鼠标相对于浏览器窗口可视区的X坐标 |
e.clientY | 返回鼠标相对于浏览器窗口可视区的Y坐标 |
e.pageX | 返回鼠标相对于文档页面的X坐标(IE9+) |
e.pageY | 返回鼠标相对于文档页面的Y坐标(IE9+) |
e.screenX | 返回鼠标相对于电脑屏幕的X坐标 |
e.screenY | 返回鼠标相对于电脑屏幕的Y坐标 |
7-2-1、鼠标跟随案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div
{
width: 200px;
height: 200px;
background: palegreen;
position: absolute;
top: 0;
left: 0;
}
</style>
</head>
<body style="height: 3000px;">
<div></div>
<script>
var div = document.querySelector('div');
// 鼠标移动触发事件: mousemove
document.addEventListener('mousemove', function(e) {
div.style.left = e.pageX + 'px';
div.style.top = e.pageY + 'px';
})
</script>
</body>
</html>
7-2-2、鼠标右键事件(自定义菜单)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {margin: 0; padding: 0; list-style: none;}
ul {
width: 200px;
border: 1px solid #000;
position: absolute;
top: 0;
left: 0;
display: none;
}
li {
padding: 10px;
border: 1px solid #333;
}
</style>
</head>
<body>
<ul>
<li>刷新</li>
<li>跳转</li>
<li>加载</li>
<li>首页</li>
</ul>
<script>
const ul = document.querySelector('ul');
document.addEventListener('contextmenu', function(e) {
ul.style.display = 'block';
const x = e.pageX;
const y = e.pageY;
ul.style.left = x + 'px';
ul.style.top = y + 'px';
// 阻止默认行为,阻止弹出默认的菜单
e.preventDefault();
});
</script>
</body>
</html>
7-2-3、鼠标拖拽案例
拖拽涉及到如下事件:
- 鼠标按下:mousedown
- 鼠标移动:mousemove
- 鼠标抬起:mouseup
- 原理:元素初始位置x y + 鼠标移动的x y
- 鼠标移动的x y 可通过当前鼠标位置 - 初始鼠标位置获得
-
实现步骤:
- 保留鼠标的初始位置与元素的初始位置
- 鼠标移动过程中不断改变元素的位置
- 获取鼠标移动的距离(当前位置 - 初始位置)
- 计算元素的最新位置(初始位置 + 移动距离)
- 鼠标抬起时,清除移动事件
-
注意:鼠标移动事件要加载document上,而不是div上,防止鼠标移动过快甩出div区域时,div就不受控制了
-
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 100px;
height: 100px;
background: palegreen;
position: absolute;
}
</style>
</head>
<body>
<div></div>
<script>
const div = document.querySelector('div');
// 1. 鼠标点击的位置
const startPos = {}
// 2. 元素的初始位置
const boxPos = {}
div.addEventListener('mousedown',function(e){
startPos.x = e.clientX;
startPos.y = e.clientY;
boxPos.x = parseFloat(getComputedStyle(div)['left']);
boxPos.y = parseFloat(getComputedStyle(div)['top']);
// 鼠标移动过程中改变元素的位置
document.addEventListener('mousemove',drag)
// 清除鼠标移动事件
document.addEventListener('mouseup',function(){
// 清除事件 - 必须清除的是命名函数!
document.removeEventListener('mousemove',drag)
},{
// 只绑定一次事件
once:true
})
})
function drag(e){
// console.log('move')
// 当前位置
var nowPos = {
x:e.clientX,
y:e.clientY
}
// 当前鼠标位置 - 初始鼠标位置 = 移动距离
var dis = {
x:nowPos.x - startPos.x,
y:nowPos.y - startPos.y
}
// 移动距离+元素的初始位置 = 元素最新的位置
var newPos = {
l:dis.x + boxPos.x,
t:dis.y + boxPos.y
}
// 限制范围 - 浏览器可视区
if(newPos.l < 0){
newPos.l = 0;
}
// 限制右侧
// 浏览器可视区宽度 - 自身宽度 = 最大left
var maxL = document.documentElement.clientWidth - div.offsetWidth;
if(newPos.l > maxL){
newPos.l = maxL;
}
if(newPos.t < 0){
newPos.t = 0;
}
// 限制下侧
// 浏览器可视区高度 - 自身高度 = 最大top
var maxT = document.documentElement.clientHeight - div.offsetHeight;
if(newPos.t > maxT){
newPos.t = maxT;
}
// 修改样式
div.style.top = newPos.t + 'px';
div.style.left = newPos.l + 'px';
// css(div,'top',newPos.t)
// css(div,'left',newPos.l)
}
</script>
</body>
</html>
八、常用的键盘事件
8-1、常用键盘事件
键盘事件 | 触发条件 |
---|---|
onkeyup | 某个键盘按键被松开时触发 |
onkeydown | 某个键盘按键被按下时触发 |
onkeypress | 某个键盘按键被按下时并弹起时触发 |
- 添加键盘事件时要添加给文档document
- onkeypress和前两个事件区别是:它不识别功能键,如左右箭头、shift等
- onkeydown与onkeyup不区分字母大小写(无论是否开启了大小写,获取keyCode时,都返回大写字母的ASCII值),onkeypress区分字母大小写
8-2、键盘事件对象常用属性
常用属性 | 描述 |
---|---|
keyCode | 键码,返回该键的ASCII值 |
key | 键值,如"a" "b" "enter" |
ctrlKey | 是否按下ctrl键,布尔值 |
altKey | 是否按下alt键,布尔值 |
shiftKey | 是否按下shift键,布尔值 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
</style>
</head>
<body>
<script>
document.addEventListener('keypress', function(e) {
// console.log(e); // keyboardEvent
console.log(e.keyCode, e.key);
console.log(e.ctrlKey);
})
</script>
</body>
</html>
8-3、键盘事件案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 200px;
height: 200px;
background: palegreen;
position: absolute;
}
</style>
</head>
<body>
<div></div>
<script>
var div = document.querySelector('div');
// 通过上下左右控制元素位置
/*
keyCode
左 : 37
上 : 38
右 : 39
下 : 40
*/
document.addEventListener('keydown', function(e) {
if (e.keyCode == 39) {
let l = parseFloat(getComputedStyle(div).left);
div.style.left = l + 5 + 'px';
// css(div, 'left', l+5);
}
if (e.keyCode == 37) {
let l = parseFloat(getComputedStyle(div).left);
div.style.left = l - 5 + 'px';
}
if (e.keyCode == 38) {
let l = parseFloat(getComputedStyle(div).top);
div.style.top = l - 5 + 'px';
}
if (e.keyCode == 40) {
let l = parseFloat(getComputedStyle(div).top);
div.style.top = l + 5 + 'px';
}
})
</script>
</body>
</html>