注册事件(绑定事件)
1. 概述
给元素添加事件,称为注册事件 / 绑定事件
注册事件有两种方式:传统方式和方法监听注册方式
2. 传统注册方式
利用 on 开头的事件
DOM 0级事件绑定方式—— 因为 W3C 在确定 DOM 版本时,在之前已经有了事实上存在的一些事件,比如 onclick 这种直接绑定给元素的属性的事件,DOM1 中用的还是之前的事件,所以是 DOM 0级事件,而不是1级事件
<button oncilck = "alert(1)"></button>
btn.onclick = function () {}
特点:注册事件的唯一性(同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数)
3. 方法监听注册方式
DOM 2级事件绑定方式
element.addEventListener()
方法
element.attachEvent()
方法
特点:同一个元素可以多次绑定事件监听,同一个事件类型可以注册多个事件函数
(1)element.addEventListener() 方法
- 第一个参数:事件类型的字符串(直接书写” click” ,不需要加 on)
- 第二个参数:事件处理函数,事件发生时,会调用该监听函数
W3C 标准,推荐方式
兼容性问题:不支持 IE9 以下的浏览器
var btn = document.getElementById("btn");
btn.addEventListener("click", function () {
alert(1);
});
// 多次绑定相同的事件类型,事件会根据书写的顺序进行一个事件排队
btn.addEventListener("click", clickEvent);
function clickEvent() {
alert(2);
}
// 点击按钮,先弹出1,再弹出2
(2)element.attachEvent() 方法
- 第一个参数:事件类型的字符串(需要加 on)
- 第二个参数:事件函数,当目标触发事件时回调函数被调用
非标准,尽量不使用
兼容性问题:只支持 IE10 及以下的浏览器
var btn = document.getElementById("btn");
btn.attachEvent("onclick", function () {
alert(3);
});
btn.attachEvent("onclick", clickEvent);
function clickEvent() {
alert(4);
}
// IE8 及以下的浏览器处理事件队列时,会出现顺序错乱
4. 注册事件的兼容写法
自定义一个注册事件函数
- 参数:事件源,事件类型(不加 on),事件函数
- IE9 及以上的浏览器,使用 addEventListener 方法
- IE9 以下的浏览器,使用 attachEvent 方法
判断浏览器时,不需要判断它的版本,可以检测浏览器能力
浏览器能力检测:将某个方法的调用作为 if 语句的判断条件,如果浏览器认识该方法返回 true,否则返回 false
var btn = document.getElementById("btn");
// 调用函数
addEvent(btn, "click", function () {
alert(1);
});
// DOM 2级事件绑定方式
// 自己制作一个兼容所有浏览器的绑定事件的函数
// 参数:事件源,事件类型,事件函数
function addEvent(ele, type, fn) {
// IE9 及以上浏览器和其他浏览器,使用 addEventListener 方法
// IE9 以下的浏览器,使用 attachEvent 方法
// 浏览器能力检测
if (ele.addEventListener) {
ele.addEventListener(type, fn);
} else if (ele.attachEvent) {
ele.attachEvent("on" + type, fn);
} else {
// 相当于 ele.onclick = fn;
// 这个 else 条件不写也可
ele["on" + type] = fn; // 传统方式
}
}
删除事件
1. 传统注册方式
例如:eventTarget.onclick = null
var btn = document.getElementById("btn");
// DOM 0级事件绑定方式
// 传统注册方式
btn.onclick = function () {
alert(1);
};
// 解除绑定方法
btn.onclick = null;
2. 方法监听注册方式
注意:没有办法移除一个匿名函数,所以在注册事件时需要单独声明一个有函数名的事件函数。
(1)element.removeEventListener() 方法
- 第一个参数:事件类型的字符串(直接书写” click” ,不需要加 on)
- 第二个参数:事件函数引用名
兼容性问题:不支持 IE9 以下的浏览器
var btn = document.getElementById("btn");
// 绑定事件
btn.addEventListener("click", fun);
// 解除绑定
btn.removeEventListener("click", fun);
function fun() {
alert(1);
}
var btn = document.getElementById("btn");
// 绑定事件
btn.addEventListener("click", fun);
function fun() {
alert(1);
// 解除绑定
btn.removeEventListener("click", fun);
}
// 点击按钮,只弹出一次1,点第二次不再弹出
(2)element.detachEvent() 方法
- 第一个参数:事件类型的字符串(需要加 on)
- 第二个参数:事件函数
兼容性问题:只支持 IE10 及以下的浏览器
var btn = document.getElementById("btn");
// 绑定事件
btn.attachEvent("onclick", fun);
// 解除绑定
btn.detachEvent("onclick", fun);
function fun() {
alert(1);
}
3. 移除事件的兼容写法
自定义一个移除事件函数
- 参数:事件源,事件类型(不加 on),事件函数
- IE9 及以上的浏览器,使用 removeEventListener 方法
- IE9 以下的浏览器,使用 detachEvent 方法
// 兼容所有浏览器的解除绑定事件的函数
// 参数:事件源,事件类型(不加 on),事件函数
function removeEvent(ele, type, fn) {
// IE9 及以上浏览器和其他浏览器,使用 removeEventListener 方法
// IE9 以下的浏览器,使用 detachEvent 方法
// 浏览器能力检测
if (ele.removeEventListener) {
ele.removeEventListener(type, fn);
} else if (ele.detachEvent) {
ele.detachEvent("on" + type, fn);
} else {
// 相当于 element.onclick = null;
// 这个 else 条件不写也可
element["on" + type] = null; // 传统方式
}
}
建议:将自己封装的一些常用函数和方法,放到一个单独的 .js 文件中
DOM 事件流
事件流描述的是从页面中接收事件的顺序
事件发生时会在元素节点之间按照待定的顺序传播,这个传播过程即 DOM 事件流
1. 事件流的三个阶段
- 第一个阶段:事件捕获
- 第二个阶段:事件执行过程
- 第三个阶段:事件冒泡
事件捕获:网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到最具体的元素接收的过程
事件冒泡:IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到 DOM 最顶层节点的过程
注意:
(1)JS 代码中只能执行捕获 / 冒泡其中的一个阶段
(2)onclick / attachEvent() 只能得到冒泡阶段
(3)addEventListener() 第三个参数为 false(或默认)时,事件冒泡
(4)addEventListener() 第三个参数为 true 时,事件捕获
(5)实际开发中很少用事件捕获,更关注事件冒泡
(6)有些事件是没有冒泡的,比如:onblur、onfocus、onmouseenter、onmouseleave
(7)事件冒泡有时会带来麻烦,有时又会很巧妙的帮助做某些事情
<div id="father">
<div id="son"></div>
</div>
<script>
var father = document.getElementById("father");
var son = document.getElementById("son");
// 事件捕获
father.addEventListener("click", fn1, true);
son.addEventListener("click", fn2, true);
function fn1() {
alert("father");
}
function fn2() {
alert("son");
}
// 当点击 son 时,先弹出 father,再弹出 son
</script>
// 事件冒泡
father.onclick = function () {
alert("father");
};
son.onclick = function () {
alert("son");
};
// 当点击 son 时,先弹出 son,再弹出 father
2. 事件委托
事件委托也称事件代理,在 jQuery 里称为事件委派
利用事件冒泡,将子级的事件委托给父级加载
同时,需要利用事件函数的一个 e 参数,内部存储的是事件对象
- 只要触发事件,函数内部都可以得到一个事件对象,对象中存储了关于事件的一系列数据
- e.target 属性记录的就是真正触发事件的事件源
案例:ul 里包含5个 li,要想点击每个 li 都弹出对话框,以前需要循环给每个 li 注册事件,而且访问 DOM 的次数越多,这就会延长整个页面的交互就绪时间
事件委托的原理:不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点(即给父节点添加侦听器,利用事件冒泡影响每一个子节点)
上一个案例用事件委托实现:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li,因为点击 li ,事件会冒泡到 ul 上,ul 有注册事件,就会触发事件监听器
事件委托的作用:只操作了一次 DOM,提高了程序的性能
// 让每个 li 被点击后,自己添加特殊的背景颜色,而其他兄弟不添加
// 事件委托:可以将一些子级的公共类型的事件委托给它们的父级添加,在父级内部想办法找到真正触发事件的最底层的事件源
// 获取 ul
var list = document.getElementById("list");
// 获取 ul 里所有的 li
var lis = list.children;
list.addEventListener("click", function (e) {
// 在内部要想办法找到真正触发事件的 li
// 借用事件函数内部的一个参数 e,e 是事件对象
// e.target 属性记录的就是真正触发事件的事件源
// 排除其他
for (var i = 0; i < lis.length; i++) {
lis[i].style.backgroundColor = "";
}
e.target.style.backgroundColor = "pink";
});
3. 事件对象
(1)什么是事件对象?
eventTarget.onclick = function (e) {}
eventTarget.addEventListener("click",function (e) {})
// 这里的 e 就是事件对象,也可以写成 event / evt
只要触发事件,就会有一个对象,内部存储了与事件相关的数据。
- 官方解释:event 对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态
- 简单解释:事件发生后,跟事件相关的一系列信息数据的集合都放在这个对象里面,这个对象就是事件对象 event ,它有很多属性和方法
e 在低版本浏览器中有兼容问题,低版本浏览器使用的是 window.event
兼容性的写法:e = e || window.event;
event 就是一个事件对象,写到侦听函数的小括号里,当形参来看
事件对象只有有了事件才会存在,它是系统给我们自动创建的,不需要我们传递参数
事件对象是我们事件的一系列相关数据的集合,跟事件相关的,比如鼠标点击,里面就包含了鼠标的相关信息(鼠标坐标等);若是键盘事件,里面就包含键盘事件的信息,比如判断用户按下了哪个键
事件对象也有兼容性问题,IE678 通过 window.event
(2)事件对象的常见属性和方法
e.eventPhase:查看事件触发时所处的阶段
- 判断出事件执行时处于哪个阶段
- 1:表示捕获阶段
- 2:表示目标阶段
- 3:表示冒泡阶段
e.target:用于获取触发事件的元素
- 标准,IE678 不识别
- 获取真正触发事件的元素
- 例如:点击了哪个元素,就返回哪个元素
e.srcElement:用于获取触发事件的元素
- 低版本浏览器使用
- 非标准,IE678 识别使用
- 兼容性写法:
var target = e.target || e.srcElement;
e.currentTarget:用于获取绑定事件的事件源元素
- IE678 无法识别
- 和 this 非常相似,都指向绑定事件的事件源元素,一般都使用 this
e.type:获取事件类型
- 比如:click、mouseover(不带 on)
- 实际应用:可以将所有给一个元素绑定的事件的事件函数写在一个函数内,通过函数内部的 e.type 判断走不同的分支
// 鼠标经过变颜色,鼠标离开后恢复为原来的颜色色
box.onmouseover = fn;
box.onmouseout = fn;
function fn(e) {
e = e || window.event;
switch (e.type) {
case "mouseover":
box.style.backgroundColor = "pink";
break;
case "mouseout":
box.style.backgroundColor = "yellowgreen";
break;
}
}
e.preventDefault():取消默认行为
- DOM 标准写法,IE678 不识别
- 比如不让链接跳转,不让提交按钮提交
e.returnValue:取消默认行为
- 非标准,低版本浏览器使用
// 让 a 链接不跳转
a.onclick = function (e) {
e = e || window.event;
alert("hello");
// 普通的方式阻止默认行为
// 只限于传统注册方式使用(没有兼容问题)
// return false;
// DOM 方法
// 普通浏览器使用,IE678 不识别
// e.preventDefault();
// 低版本浏览器需要使用一个对象的属性
e.returnValue = false;
};
4. 阻止事件冒泡
(1)e.stopPropagation();
- 阻止冒泡,标准方式
(2)e.cancelBubble = true;
- 阻止冒泡
- IE 低版本,标准中已废弃(非标准,IE678 使用)
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var box1 = document.getElementById("box1");
var box2 = document.getElementById("box2");
var box3 = document.getElementById("box3");
var array = [box1, box2, box3];
for (var i = 0; i < array.length; i++) {
var box = array[i];
box.onclick = function (e) {
e = e || window.event;
console.log(this.id);
// 阻止事件冒泡
// e.stopPropagation();
// 低版本浏览器使用 属性
e.cancelBubble = true;
};
}
</script>
</body>
/* css 代码 */
#box1 {
width: 200px;
height: 200px;
background-color: pink;
}
#box2 {
width: 150px;
height: 150px;
background-color: skyblue;
}
#box3 {
width: 100px;
height: 100px;
background-color: greenyellow;
}
5. 常用的鼠标事件
(1)常用的鼠标事件
contextmenu
- 禁止鼠标右键菜单
- 主要控制应该何时显示上下文菜单,主要用于程序员取消默认的上下文菜单
// 禁止鼠标右键菜单
document.addEventListener("contextmenu", function (e) {
e.preventDefault();
});
selectstart
- 禁止鼠标选中
// 禁止鼠标选中
document.addEventListener("selectstart", function (e) {
e.preventDefault();
});
(2)鼠标事件对象
e.clientX/e.clientY
- 鼠标距离浏览器窗口左上角的距离
- 所有浏览器都支持
e.pageX/e.pageY
- 鼠标距离整个HTML文档页面左上顶点的距离,
- IE8 以前不支持
e.screenX/e.screenY
- 鼠标距离电脑屏幕左上顶点的距离
- IE8 以前不支持
案例:图片跟随鼠标移动效果
<body>
<img src="../images/tianshi.gif" id="pic" />
<script>
var pic = document.getElementById("pic");
// 通过鼠标移动事件给图片添加 left 和 top 的值
// 给整个文档添加鼠标移动事件
document.onmousemove = function (e) {
e = e || window.event;
var x = e.clientX;
var y = e.clientY;
// 给元素的 css 属性赋值
pic.style.left = x - 50 + "px";
pic.style.top = y - 40 + "px";
// 鼠标默认显示在图片左上角
// 其中 -50 和 -40 是减去图片长宽的一半,为了使鼠标显示在图片中心,
};
</script>
</body>
/* css 代码 */
* {
margin: 0;
padding: 0;
}
#pic {
/* fixed 参考元素:浏览器窗口 */
/* absolute 如果祖先都没有定位,参考body */
position: fixed;
}
6. 常用的键盘事件
(1)常用键盘事件
onkeyup
- 某个键盘按键被松开时触发
onkeydown
- 某个键盘按键被按下时触发
onkeypress
- 某个键盘按键被按下时触发
- 但它不识别功能键(ctrl、shift、箭头等)
注意:
- 如果使用 addEventListener 不需要加 on
- 三个事件的执行顺序是:keydown → keypress → keyup
document.addEventListener("keydown", function () {
console.log("我按下了");
});
document.onkeyup = function () {
console.log("我弹起了");
};
(2)键盘事件对象
key
- 可以得到按的是哪个键
- 比如:按下 a ,返回 a
- 有兼容问题
keyCode
- 返回该键的 ASCII 值,是属性
- keyup 和 keydown 事件不区分字母大小写,例如:a 和 A 得到的都是65
- keypress 事件区分字母大小写,例如:a 97 和 A 65
document.addEventListener("keydown", function () {
console.log("我按下了");
});
document.onkeyup = function () {
console.log("我弹起了");
};
document.addEventListener("keydown", function (e) {
console.log(e.keyCode); // 例如按键盘上的 a/A ,都返回 65
// 可以利用 keyCode 返回的 ASCII 码值来判断用户按下了哪个键
if (e.keyCode === 65) {
alert("您按下的a键");
} else {
alert("您没有按下a键");
}
});
document.addEventListener("keypress", function (e) {
console.log(e.keyCode); // 例如按键盘上的a得到97,A得到65
});
// 实际开发中,更多使用 keydown 和 keyup,它能识别所有的键(包括功能键)
// keypress 不识别功能键,但 keyCode 属性能区分大小写,返回不同的 ASCII 值
MDN web 事件参考:MDN
案例:模拟京东按键输入内容(当我们按下 s 键,光标就定位到搜索框)
核心思路:检测用户是否按下了 s 键,若按下 s 键,就把光标定位到搜索框里
搜索框获得焦点:使用 js 里的 focus() 方法
var search = document.querySelector("input");
// 用 keydown 的话,会导致 s 写入搜索框内,而 keyup 是弹起后才触发,不会写入 s
document.addEventListener("keyup", function (e) {
// console.log(e.keyCode);
// 按下 s 键,得出 s 的 ASCII 值是83
if (e.keyCode === 83) {
search.focus();
}
});
案例:模拟京东快递单号查询
当我们在文本框输入内容时,文本框上面自动显示大字号的内容,提高用户体验
<body>
<div class="search">
<div class="con"></div>
<input type="text" class="jd" placeholder="请输入您的快递单号" />
</div>
<script>
var con = document.querySelector(".con");
var jd_input = document.querySelector(".jd");
// 检测用户输入
jd_input.addEventListener("keyup", function () {
// 若快递单号为空,则隐藏大号字体盒子
if (this.value == "") {
con.style.display = "none";
} else {
con.style.display = "block";
// 把快递单号里面的值获取过来赋值给 con 盒子作为内容
con.innerText = this.value;
}
});
// 失去焦点时,隐藏 con 盒子
jd_input.addEventListener("blur", function () {
con.style.display = "none";
});
// 获得焦点时,如果 innput 输入框不为空,显示 con 盒子
jd_input.addEventListener("focus", function () {
if (this.value !== "") {
con.style.display = "block";
}
});
</script>
</body>
/* css 代码 */
* {
margin: 0;
padding: 0;
}
.search {
position: relative;
width: 178px;
margin: 100px;
}
.con {
display: none;
position: absolute;
top: -40px;
width: 161px;
padding: 5px 0;
border: 1px solid rgba(0, 0, 0, 0.2);
font-size: 18px;
color: #333;
line-height: 20px;
box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2);
}
.con::before {
content: "";
position: absolute;
top: 28px;
left: 18px;
width: 0;
height: 0;
border: 8px solid #000;
border-style: solid dashed dashed;
border-color: #fff transparent transparent;
}
注意:
- keydown 和 keypress 在文本框里面的特点:它们两个事件触发的时候,文字还没有落入文本框中
- keyup 事件触发的时候,文本已经落入文本框里面了
/* css 代码 */
* {
margin: 0;
padding: 0;
}
.search {
position: relative;
width: 178px;
margin: 100px;
}
.con {
display: none;
position: absolute;
top: -40px;
width: 161px;
padding: 5px 0;
border: 1px solid rgba(0, 0, 0, 0.2);
font-size: 18px;
color: #333;
line-height: 20px;
box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2);
}
.con::before {
content: "";
position: absolute;
top: 28px;
left: 18px;
width: 0;
height: 0;
border: 8px solid #000;
border-style: solid dashed dashed;
border-color: #fff transparent transparent;
}
注意:
- keydown 和 keypress 在文本框里面的特点:它们两个事件触发的时候,文字还没有落入文本框中
- keyup 事件触发的时候,文本已经落入文本框里面了