[TOC]
# 事件
  事件是可以被控件识别的操作,如按下确定按钮,选择某个单选按钮或者复选框。每一种控件有自己可以识别的事件,如窗体的加载、单击、双击等事件,编辑框(文本框)的文本改变事件,等等。
  事件是基于 触发-响应 机制实现的,当用户对控件做某些**操作**时,如 点击,移入鼠标,输入文字等,控件会识别到该操作,并作出对应的**响应**。
## 事件三要素(掌握)
1. **事件源**: 触发事件的元素(被动)
2. **事件名称**: click 点击事件
3. **事件处理函数**: 事件触发后要执行的代码(函数形式)
## 事件的基本使用
注册事件,必须使用**事件三要素**:
**1、事件源 2、事件名称 3、事件处理函数**
html和css代码
```
<input id="hitme" type="button" value="点我!"/>
```
JavaScript代码
```
//1、获取按钮对象
var hitme = document.getElementById('hitme');
console.log(hitme);
//2、注册(绑定)点击事件
hitme.onclick = function () {
alert('别点我,疼!')
}
```
<br>
# **注册事件的三种方式**
  这节课开始,我们来深入学习事件。首先,回顾以前注册事件的做法,在团队开发中,当一个按钮要被多个用户使用时,多个用户可能会对一个按钮进行事件注册,此时每个用户都的代码都写在了 js 文件中,当引入多个js文件时,后面引入的js会覆盖掉前面引入的js的对按钮的操作。即前面引入的js中的注册事件会被后面引入的js的注册事件覆盖掉。
  下面对事件相关的方法进行详细学习,以解决以上存在的事件覆盖的问题。
**addEventListener**,将指定的监听器注册到 EventTarget(事件目标)上,当该对象触发指定的事件时,指定的回调函数就会被执行。说白了,就是addEventListener可以将时间监听器注册到btn按钮上,当事件发生时,调用事件处理函数。
1、以前的方式:无法给一个对象注册多个同种事件 2、addEventListener方式:有浏览器兼容性问题
  html和css代码
```
<input id="btn" type="button" value="按钮"/>
```
  JavaScript代码
```
var oBtn =document.getElementById('btn');
// 后写的事件会覆盖先写的事件
// 方式一
/*oBtn.onclick=function() {
alert('注册事件1'); //弹出框
};
oBtn.onclick=function() {
alert('注册事件2');
};*/
//方式二
/*oBtn['onclick']=function () {
alert('注册事件01');
};
*/
//方式三
oBtn.addEventListener('click',function () {
alert('注册事件001');
},false)
//在 addEventListener('click',function () {alert('注册事件001');},false) 中吗,
// 第一个参数 'click' 是事件类型,不加 on ;
//第二个参数 function () {alert('注册事件001');} 是事件处理函数;
//第三个参数 false 是布尔类型,默认值为 false ;
true捕获阶段调用事件处理方法;false冒泡阶段调用事件处理方法。
```
### 注册事件的兼容性问题
  下面来看,注册事件的时候,浏览器的兼容性问题,我们学习过以下三种种注册事件存在的问题: 1、以前:无法给同一个对象的同一个事件注册多个事件处理函数 2、addEventListener:有浏览器兼容性问题,是DOM中的标准方法。 3、attachEvent:是IE中特有的方法
  要解决这个问题,我们就得利用自定义函数,自己写函数去实现。
html和css代码
```
<button id="btn">按钮</button>
```
JavaScript代码
```
var oBtn =document.getElementById('btn');
addEventListener1(oBtn,'click',function () {
alert('已处理兼容性问题');
},false); //这里 false 可写可不写,默认值是 false ;
// 创建一个addEventListener的方法,参数: element--事件源 type--事件类型 fn--执行函数
function addEventListener(element,type,fn){
if(element.addEventListener){ //判断浏览器是否支持 addEventListener ,若符合,则执行下一行代码。
element.addEventListener(type,fn);
}else if (element.attachEvent){ //判断该浏览器是否支持 attachEvent ,若符合,则执行下一行代码。
element.attachEvent('on'+type,fn);
}else{
element['on'+type]=fn; //如果都不支持,就直接使用element['onclick']的方法
}
}
// attachEvent——兼容:IE7、IE8;不兼容firefox、chrome、IE9、IE10、IE11、safari、opera
// addEventListener——兼容:firefox、chrome、IE、safari、opera;不兼容IE7、IE8
```
  以上方式一般既可以解决这几个方法的浏览器兼容性问题,但是在极端情况下还是有可能存在不支持的浏览器,庆幸的是,当前主流浏览器都支持了addEventListener,即使是老版本的IE浏览器也能用attachEvent解决,最后一种判断的写法element\["on"+eventName\] ,是作为一种极端情况下的解决方案。
<br>
## 移除事件的三种方式
  在开发中,如果想让按钮的事件处理函数只能执行一次,怎么办?如何移除元素的事件?
  html和css代码
```
<button id="btn">按钮</button>
```
  JavaScript代码
```
//如何移除元素的事件?
var oBtn = document.getElementById('btn');
```
```
// 第一种移除事件的方法
oBtn.onclick = function(){
alert(1111);
oBtn.onclick = null;
}
```
```
// 第二种
oBtn.addEventListener('click', fn)
function fn(){
alert(111);
oBtn.removeEventListener('click', fn); // removeEventListener第二个参数只能接收非匿名函数
}
```
```
// 第三种,兼容ie9-11
oBtn.attachEvent('onclick', fn);
function fn(){
alert(1111);
oBtn.detachEvent('onclick', fn);
```
>[info] 小结
> 移除事件的三种方式小结如下:
> 1、onclick:让按钮的事件处理函数只能执行一次,onclick=null ;
> 2、removeEventListener:如果想要移除事件,注册事件的时候就不能使用匿名函数;
> 3、detachEvent:谷歌中不支持,IE9-IE11中支持,了解即可;
### 移除事件的兼容性问题
解决移除事件的兼容性问题 跟 解决注册事件兼容性问题的函数相似。 注意,我们的代码知识作为一个演示,在这里作为了解即可,以后会使用使用框架,框架中已经帮我们做了很好的兼容性处理。
```
var btn = document.getElementById('btn');
addEventListener(btn, 'click', fn);
//事件处理函数
function fn() {
alert('已经兼容主流浏览器的事件移除');
removeEventListener(btn, 'click', fn);
}
//解决移除事件的浏览器兼容性问题
//创建一个removeEventListener方法,用来兼容移除事件
//element 元素, type 事件类型 fn 事件处理函数
function removeEventListener(element, type, fn) {
if (element.removeEventListener) {
element.removeEventListener(type, fn);
}else if (element.detachEvent) {
//处理IE6-IE11之前的版本
element.detachEvent('on'+type, fn);
}else {
// 相当于element.onclick = null
element['on'+type]=null;
}
}
```
## 事件的三个阶段
捕获阶段 目标阶段 冒泡阶段
下面来学习addEventListener的第三个参数的作用,通过查文档可以知,addEventListener的第三个参数是一个布尔类型。 1、第三个参数是false时,事件从里到外执行,这种效果叫事件冒泡; 2、第三个参数是true时,事件从外到里执行,执行顺序颠倒过来了,这种效果叫做事件捕获;
html和css代码
```
<style>
#box1 {
width: 300px;
height: 300px;
background-color: blue;
}
#box2 {
width: 200px;
height: 200px;
background-color: yellow;
}
#box3 {
width: 100px;
height: 100px;
background-color: red;
}
</style>
<script src="common.js"></script>
<div id="box1">
<div id="box2">
<div id="box3">
</div>
</div>
</div>
```
JavaScript代码
```
//addEventListener的第三个参数的作用
var box1 = my$('box1');
var box2 = my$('box2');
var box3 = my$('box3');
var array = [box1, box2, box3];
for (var i = 0; i < array.length; i++) {
array[i].addEventListener('click', function () {
//输出事件源的id
console.log(this.id);
}, true);
}
//以上代码,在点击box3的时候,除了box3本身的事件会触发,还会触发box2和box1的事件,而且这种触发顺序是从里到外,这种效果叫做 事件冒泡
for (var i = 0; i < array.length; i++) {
array[i].addEventListener('click', function () {
//输出事件源的id
console.log(this.id);
}, false);
}
//以上代码,在点击box3的时候,除了box3本身的事件会触发,还会触发box2和box1的事件,而且这种触发顺序是从外到里,这种效果叫做 事件捕获
```
>[info]事件发生的时候,要经过事件的三个阶段,我们常常使用的是事件冒泡阶段,而其他两个阶段不能人为干预。
在JavaScript中,事件有以下三个阶段 第一阶段:捕获阶段 第二阶段:目标阶段(执行当前点击的元素) 第三阶段:冒泡阶段
第一阶段:捕获阶段
![](https://img.kancloud.cn/c9/52/c9520a5d38353e44c93840b884bff7ea_452x288.png)
第二阶段:目标阶段(执行当前点击的元素)
第三阶段:冒泡阶段
![](https://img.kancloud.cn/6b/22/6b22c476e554e66b12178da390f4431f_449x283.png)
注意,注册事件有三种,其中onclick、attachEvent没有第三个参数,实际上我们无法通过onclick、attachEvent来干预事件的第一阶段和第二阶段, 因为onclick、attachEvent都只有两个参数, 而且onclick、attachEvent注册的事件默认是冒泡阶段。很多时候我们只关心事件的第三阶段,即冒泡阶段。
//事件的阶段
// 第一阶段:事件捕获
// 第二阶段:目标阶段(执行当前点击的元素)
// 第三阶段:事件冒泡
// onclick、attachEvent只有事件冒泡阶段
/*for (var i = 0; i < array.length; i++) {
array[i].attachEvent('onclick',function () {
console.log(this.id);
})
}*/
for (var i = 0; i < array.length; i++) {
array[i].onclick=function () {
console.log(this.id);
}
}
document.body.onclick=function () {
console.log(this);
}
document.onclick=function () {
console.log(this);
}
### 事件委托(掌握)
案例
选择美女:当点击li时,选中li
在事件冒泡中,怎么获取被点击的元素?我们可以利用事件冒泡的特点:当子元素的事件发生时,父元素的同名事件也会发生。
利用事件冒泡的特点:当点击子元素时,父元素的同名事件也会发生。
知识点: 1、事件委托:原理就是事件冒泡。此处的事件委托是,当li被点击时,li并没有做事件处理,而是委托ul做了事件处理,ul事件中可以使用事件对象e获取target属性,完成事件处理。 2、事件对象(事件处理函数的参数e)
html和css代码
~~~
<ul id="ul">
<li>西施</li>
<li>貂蝉</li>
<li>昭君</li>
<li>凤姐</li>
<li>芙蓉姐姐</li>
</ul>
~~~
JavaScript代码
~~~
//事件冒泡的应用(事件委托)
my$('ul').onclick = function (e) {
//e是事件对象,通过事件对象可以获取到触发事件的真正的元素相关的信息
// console.log(e);
// console.log(this);
//e.target:获取真正触发事件的那个元素
// console.log(e.target);
var target = e.target;
target.style.backgroundColor='lightblue';
}
~~~
**小结**
~~~
知识点:事件委托,事件对象;
事件处理函数在事件发生时,由系统去调用,系统在调用事件处理函数时,会传入事件对象,所以我们可以直接使用事件对象。
~~~
### 事件对象的属性(掌握)
通过事件对象,我们可以获取到事件发生的时候,和事件相关的一些数据。事件对象e的IE浏览器中存在兼容性问题,可以通过window.event获取事件对象以解决兼容性问题。下面演示事件对象的属性的使用和兼容性问题。
事件对象的三个属性 eventPhase:获取事件的阶段,数值表示 target:真正触发事件的元素,IE兼容性问题用 e.srcElement currentTarget:相当于this
html和css代码
~~~
<style>
#box1 {
width: 300px;
height: 300px;
background-color: blue;
}
#box2 {
width: 200px;
height: 200px;
background-color: yellow;
}
#box3 {
width: 100px;
height: 100px;
background-color: red;
}
</style>
<script src="common.js"></script>
<input type="button" id="btn" value="事件按钮"/>
<div id="box1">
<div id="box2">
<div id="box3">
</div>
</div>
</div>
~~~
JavaScript代码
~~~
//事件对象的属性的使用和兼容性问题
var box1 = my$('box1');
var box2 = my$('box2');
var box3 = my$('box3');
var array = [box1, box2, box3];
my$('btn').onclick = function (e) {
//当按钮被点击时,事件处理函数被系统调用,系统调用事件处理函数的时候,会传入事件对象。
//我们可以通过事件对象获取到事件的各种信息
//老版本的IE中,获取事件对象有兼容性问题
e = e || window.event;
//获取事件的阶段,数值表示 捕获阶段 1 目标阶段 2 冒泡阶段 3
console.log(e.eventPhase);
//获取真正触发事件的对象
// 获取触发事件的元素,老版本的IE中,使用srcElement获取
var target = e.target || e.srcElement;
console.log(target);
//当前执行事件的元素,相当于this
console.log(e.currentTarget);
}
~~~
事件冒泡中的三个属性代表的对象不一样 eventPhase:目标阶段、冒泡阶段 target:IE兼容性问题用 e.srcElement currentTarget:相当于this
~~~
for (var i = 0; i < array.length; i++) {
array[i].onclick = function (e) {
e = e || window.event;
//获取事件的阶段,数值表示 捕获阶段 1 目标阶段 2 冒泡阶段 3
console.log(e.eventPhase);
//获取真正触发事件的对象
// 老版本的IE中,使用srcElement获取
var target = e.target || e.srcElement;
console.log(target);
//相当于this
console.log(e.currentTarget);
console.log(this);
}
}
~~~
event.type:获取事件名称。 应用场景:给多个事件指定同一个函数 好处:多个事件只使用了一个函数,减少内存的消耗
html和css代码
~~~
<style>
body {
margin: 0;
}
#box {
margin: 100px;
width: 200px;
height: 200px;
background-color: blue;
}
</style>
<script src="common.js"></script>
<div id="box"></div>
~~~
JavaScript代码
~~~
//event.type:获取事件名称。
my$('box').onmouseover = function (e) {
e = e || window.event;
console.log(e.type);
}
my$('box').onclick = fn;
my$('box').onmouseover = fn;
my$('box').onmouseout = fn;
//当多个(种)事件使用同一个事件处理函数时,可以使用e.type获取到当前发生的事件的名称
//多个事件只使用了一个函数,减少内存的消耗
function fn(e) {
e = e || window.event;
//根据事件名称,执行不同的业务代码
switch (e.type) {
case 'click':
console.log('click事件');
break;
case 'mouseover':
console.log('鼠标移入');
break;
case 'mouseout':
console.log('鼠标移出');
break;
}
}
~~~
当事件发生时,通过事件对象可以获取鼠标的位置坐标 ,如下:
1、e.clientX 和 e.clientY,获取相对于可视区域鼠标位置坐标,所有浏览器都支持; 2、e.pageX 和 e.pageY,获取相对于整个文档的的位置,IE9以后开始支持;
HTML和css代码
~~~
<style>
body {
margin: 0;
height: 1000px;
}
</style>
~~~
JavaScript代码
~~~
document.onclick=function (e) {
//获取可视区域内的鼠标的坐标,
// console.log(e.clientX);
// console.log(e.clientY);
//获取相对于整个文档的的位置。
console.log(e.pageX);
console.log(e.pageY);
}
~~~
~~~
1、当事件发生时,通过事件对象可以获取鼠标相对于可视区域的位置坐标
e.clientX 和 e.clientY ,所有浏览器都支持,窗口位置;设置滚动条进行演示。
2、当事件发生时,通过事件对象可以获取鼠标相对于整个文档的的位置坐标
e.pageX 和 e.pageY
~~~
#### 需求
1、跟着鼠标飞的天使:当鼠在浏览器页面移动时,让天使(一张图片)跟着鼠标移动。
需求分析:当鼠标在文档中移动时,让图片的坐标跟着鼠标的坐标的变化而变化,即鼠标的位置和图片的位置保持相对静止。
html和css代码
~~~
<style>
body {
height: 1000px;
}
#ts {
position: absolute;
}
</style>
<script src="common.js"></script>
<img src="images/tianshi.gif" id="ts" alt="">
~~~
JavaScript代码
~~~
//1、鼠标移动事件
document.onmousemove=function (e) {
// console.log(e.clientX, e.clientY);
//在可视区域内移动
// my$('ts').style.left = e.clientX + 'px';
// my$('ts').style.top = e.clientY+ 'px';
//在整个文档中移动
my$('ts').style.left = e.pageX - 20 + 'px';
my$('ts').style.top = e.pageY - 20 + 'px';
}
~~~
2、获取鼠标在文档中距离顶部的距离
(1)e.clientX 和 e.clientY,获取相对于可视区域鼠标位置坐标,所有浏览器都支持; (2)e.pageX 和 e.pageY,获取相对于整个文档的的位置,IE9以后开始支持;
解决pageX和pageY的兼容性问题,思路:pageY = clientY+页面滚动出去的距离;
获取页面滚动出去的距离,document.body是文档的body元素: document.body.scrollLeft document.body.scrollTop
有些浏览器是使用document.documentElement(文档根元素 html)获取滚动出去的距离: document.documentElement.scrollLeft document.documentElement.scrollTop
为了解决浏览器兼容性问题,我们需要使用到document.body和document.documentElement两种对象。
html和css代码
~~~
<style>
body {
height: 1000px;
}
#ts {
position: absolute;
}
</style>
~~~
JavaScript代码
~~~
//获取页面滚出去的距离
// 需求:获取鼠标在文档中距离顶部的距离
// 思路:鼠标距离文档顶部的距离 = clientY + 页面被滚动处理的长度
document.onclick=function (e) {
//处理事件对象的兼容性问题
e = e || window.event;
//获取被滚动出去的页面的长度
console.log(document.body.scrollLeft);
console.log(document.body.scrollTop);
//文档元素 html元素
console.log(document.documentElement);
console.log(document.documentElement.scrollLeft);
console.log(document.documentElement.scrollTop);
}
//获取页面滚出去的距离
function getScroll(){
var scrolLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
return{
scrolLeft:scrolLeft,
scrollTop:scrolLeft
}
}
~~~
解决pageX和pageY的兼容性问题,e.pageX || e.clientX+getscroll().scrollLeft
~~~
// 需求:获取鼠标在文档中距离顶部的距离
// 思路:鼠标距离文档顶部的距离 = clientY + 页面被滚动处理的长度
document.onclick = function (e) {
console.log(getPage(e).pageX, getPage(e).pageY);
}
// 鼠标距离文档顶部的距离 = clientY + 页面被滚动处理的长度
function getPage(e) {
var pageX = e.pageX || e.clientX + getScroll().scrolLeft;
var pageY = e.pageY || e.clientY + getScroll().scrollTop;
return{
pageX:pageX,
pageY:pageY
}
}
// 注意: IE中document.onclick事件无效, 需使用document.body.onclick进行测试
~~~
需求:获取鼠标在div中的坐标; 需求分析:鼠标在div中的位置 = 鼠标在文档中的坐标 - div在文档中的坐标(偏移量); div的偏移量:offsetLeft、offsetTop;
html和css代码
~~~
<style>
body {
margin: 0;
}
#box {
width: 300px;
height: 300px;
border: 1px solid red;
margin: 103px 10px 10px 103px;
}
</style>
<div id="box">
</div>
~~~
JavaScript代码
~~~
my$('box').onclick=function (e) {
console.log(getPage(e).pageX);
console.log(getPage(e).pageY);
//元素在文档中的坐标(偏移量)
console.log(this.offsetLeft);
console.log(this.offsetTop);
//获取鼠标在div中的坐标
var divLeft = getPage(e).pageX - this.offsetLeft;
var divTop = getPage(e).pageY - this.offsetTop;
console.log(divLeft, divTop);
}
~~~
### 阻止事件传播(掌握)
事件传播的经典行为是事件冒泡,下面介绍如何阻止事件冒泡。
我们曾使用return false取消a标签的默认跳转行为,除了这种写法,DOM中也提供了阻止a标签默认行为的标准方法 e.preventDefault(),而在IE老版本中则使用 e.returnValue = false;
html和css代码
~~~
<a id="link" href="http://www.baidu.com">我寻你千百度</a>
<style>
#box1 {
width: 300px;
height: 300px;
background-color: blue;
}
#box2 {
width: 200px;
height: 200px;
background-color: yellow;
}
#box3 {
width: 100px;
height: 100px;
background-color: green;
}
</style>
<script src="common.js"></script>
<div id="box1">
<div id="box2">
<div id="box3">
</div>
</div>
</div>
~~~
JavaScript代码
~~~
my$('link').onclick = function (e) {
alert('跳转吧');
// return false;
//DOM中的标准方法
// e.preventDefault();
//IE的老版本中使用e.returnValue
e.returnValue = true;
}
~~~
停止事件传播->阻止事件冒泡: 标准方法 event.stopPropagation(); IE低版本 event.cancelBubble = true; 标准中已废弃
~~~
for (var i = 0; i < array.length; i++) {
array[i].onclick = function (e) {
console.log(this.id);
//阻止冒泡
// Propagation:传播
// - 标准方式 event.stopPropagation();
// e.stopPropagation();
// - IE低版本 event.cancelBubble = true; 标准中已废弃
e.cancelBubble = true;
}
}
~~~
### 常用的鼠标和键盘事件(掌握)
需求: 控制输入年龄的文本框,使其只能输入数字和删除输入的数字 。
在开发中,经常会看到这样的需求 “ 控制输入年龄的文本框,使其只能输入数字和删除输入的数字 ” 。用户在输入数字时,需要按下键盘进行输入,怎么知道键盘是否按下呢?要完成这种需求,我们需要学习键盘事件!
onkeyup事件:键盘按键抬起触发 ; onkeydown事件:键盘按键按下触发;
键盘码 e.keyCode的值在 48-57 之间对应着键盘上的数字,回退(删除)按键的键盘码是 8,取消键盘按下时的默认行为(所谓默认行为是指:按键的值落到文本框内)。
html和css代码
~~~
年龄:<input type="text" name="userAge" id="tx"/>
~~~
JavaScript代码
~~~
my$('tx').onkeydown = function (e) {
//处理事件对象的浏览器兼容性问题
e = e || window.event;
// e.keyCode: 键盘码, 在[48, 57]区间内的键盘码,对应着十个数值
// console.log(e.keyCode);
if ((e.keyCode < 48 || e.keyCode > 57) && e.keyCode !== 8) {
//取消键盘输出的默认行为的执行, 按键的值将不落到文本框中
// return false;
e.preventDefault();
}
}
~~~
**小结**
~~~
通过前面的学习,我们学习了一下这些事件,小结如下。
onkeyup事件:键盘按键抬起触发 ;
onkeydown事件:键盘按键按下触发;
onclick事件:元素被点击时触发;
onmouseup事件:鼠标按键放开时触发;
onmousedown事件:鼠标按键按下触发;
onmousemove事件:鼠标移动触发;
~~~