事件简介
事件的三要素
事件的三要素:事件源、事件、事件驱动程序。
- 事件源:引发后续事件的html标签。
- 事件:js已经定义好了(见下图)。
- 事件驱动程序:对样式和html的操作。也就是DOM。
也就是说,我们可以在时间对应的属性中写一些js代码,当事件被触发时,这些代码将会执行。
代码书写步骤如下:(重要)
- (1)获取事件源:document.getElementById(“box”); // 类似于Android里面的findViewById
- (2)绑定事件: 事件源box.事件onclick = function(){ 事件驱动程序 };
- (3)书写事件驱动程序:关于DOM的操作。
最简单的代码举例:(点击box1,然后弹框)
<body>
<div id="box1"></div>
<script type="text/javascript">
// 1、获取事件源
var div = document.getElementById("box1");
// 2、绑定事件
div.onclick = function () {
// 3、书写事件驱动程序
alert("我是弹出的内容");
}
</script>
</body>
常见的事件如下:
下面针对这事件的三要素,进行分别介绍。
1、获取事件源的方式(DOM节点的获取)
获取事件源的常见方式如下:
var div1 = document.getElementById("box1"); //方式一:通过id获取单个标签
var arr1 = document.getElementsByTagName("div"); //方式二:通过 标签名 获得 标签数组,所以有s
var arr2 = document.getElementsByClassName("hehe"); //方式三:通过 类名 获得 标签数组,所以有s
2、绑定事件的方式
方式一:直接绑定匿名函数
<div id="box1" ></div>
<script type="text/javascript">
var div1 = document.getElementById("box1");
//绑定事件的第一种方式
div1.onclick = function () {
alert("我是弹出的内容");
}
</script>
方式二:先单独定义函数,再绑定
<div id="box1" ></div>
<script type="text/javascript">
var div1 = document.getElementById("box1");
//绑定事件的第二种方式
div1.onclick = fn; //注意,这里是fn,不是fn()。fn()指的是返回值。
//单独定义函数
function fn() {
alert("我是弹出的内容");
}
</script>
注意上方代码的注释。绑定的时候,是写fn,不是写fn()。fn代表的是整个函数,而fn()代表的是返回值。
方式三:行内绑定
<!--行内绑定-->
<div id="box1" onclick="fn()"></div>
<script type="text/javascript">
function fn() {
alert("我是弹出的内容");
}
</script>
注意第一行代码,绑定时,是写的"fn()"
,不是写的"fn"
。因为绑定的这段代码不是写在js代码里的,而是被识别成了字符串。
3、事件驱动程序
我们在上面是拿alert举例,不仅如此,我们还可以操作标签的属性和样式。举例如下:
点击鼠标时,原本粉色的div变大了,背景变红:
<style>
#box1 {
width: 100px;
height: 100px;
background-color: pink;
cursor: pointer;
}
</style>
</head>
<body>
<div id="box1" ></div>
<script type="text/javascript">
var div1 = document.getElementById("box1");
//点击鼠标时,原本粉色的div变大了,背景变红了
div1.onclick = function () {
div1.style.width = "200px"; //属性值要写引号
div1.style.height = "200px";
div1.style.backgroundColor = "red"; //属性名是backgroundColor,不是background-color
}
</script>
上方代码的注意事项:
- 在js里写属性值时,要用引号
- 在js里写属性名时,是
backgroundColor
,不是CSS里面的background-color
。
绑定事件的两种方式/DOM事件的级别
DOM0的写法:onclick
element.onclick = function () {
}
举例:
<body>
<button>点我</button>
<script>
var btn = document.getElementsByTagName("button")[0];
//这种事件绑定的方式,如果绑定多个,则后面的会覆盖掉前面的
btn.onclick = function () {
console.log("事件1");
}
btn.onclick = function () {
console.log("事件2");
}
</script>
</body>
点击按钮后,上方代码的打印结果:
事件2
我们可以看到,DOM对象.事件 = 函数
的这种绑定事件的方式:一个元素的一个事件只能绑定一个响应函数。如果绑定了多个响应函数,则后者会覆盖前者。
DOM2的写法:addEventListener(高版本浏览器)
element.addEventListener('click', function () {
}, false);
参数解释:
- 参数1:事件名的字符串(注意,没有on)
- 参数2:回调函数:当事件触发时,该函数会被执行
- 参数3:true表示捕获阶段触发,false表示冒泡阶段触发(默认)。如果不写,则默认为false。【重要】
举例:
<body>
<button>按钮</button>
<script>
var btn = document.getElementsByTagName("button")[0];
// addEventListener: 事件监听器。 原事件被执行的时候,后面绑定的事件照样被执行
// 这种写法不存在响应函数被覆盖的情况。(更适合团队开发)
btn.addEventListener("click", fn1);
btn.addEventListener("click", fn2);
function fn1() {
console.log("事件1");
}
function fn2() {
console.log("事件2");
}
</script>
</body>
点击按钮后,上方代码的打印结果:
事件1
事件2
我们可以看到,addEventListener()
这种绑定事件的方式:
- 一个元素的一个事件,可以绑定多个响应函数。不存在响应函数被覆盖的情况。执行顺序是:事件被触发时,响应函数会按照函数的绑定顺序执行。
- addEventListener()中的this,是绑定事件的对象。
-
addEventListener()
不支持 IE8 及以下的浏览器。在IE8中可以使用attachEvent
来绑定事件(详见下一小段)。
DOM2的写法:attachEvent(IE8及以下版本浏览器)
element.attachEvent('onclick', function () {
});
参数解释:
- 参数1:事件名的字符串(注意,有on)
- 参数2:回调函数:当事件触发时,该函数会被执行
举例:
<body>
<button>按钮</button>
<script>
var btn = document.getElementsByTagName('button')[0];
btn.attachEvent('onclick', function() {
console.log('事件1');
});
btn.attachEvent('onclick', function() {
console.log('事件2');
});
</script>
</body>
在低版本的IE浏览器上,点击按钮后,上方代码的打印结果:
事件2
事件1
我们可以看到,attachEvent()
这种绑定事件的方式:
- 一个元素的一个事件,可以绑定多个响应函数。不存在响应函数被覆盖的情况。注意:执行顺序是,后绑定的先执行。
- attachEvent()中的this,是window
兼容性写法
上面的内容里,需要强调的是:
-
addEventListener()
中的this,是绑定事件的对象。 -
attachEvent()
中的this,是window。
既然这两个写法的this
不同,那么,有没有一种兼容性的写法可以确保这两种绑定方式的this是相同的呢?我们可以封装一下。代码如下:
<body>
<button>按钮</button>
<script>
var btn = document.getElementsByTagName('button')[0];
myBind(btn , "click" , function(){
alert(this);
});
//定义一个函数,用来为指定元素绑定响应函数
/*
* addEventListener()中的this,是绑定事件的对象
* attachEvent()中的this,是window
* 需要统一两个方法this
*/
/*
* 参数:
* element 要绑定事件的对象
* eventStr 事件的字符串(不要on)
* callback 回调函数
*/
function myBind(element , eventStr , callback){
if(element.addEventListener){
//大部分浏览器兼容的方式
element.addEventListener(eventStr , callback , false);
}else{
/*
* this是谁,由调用方式决定
* callback.call(element)
*/
//IE8及以下
element.attachEvent("on"+eventStr , function(){
//在匿名函数 function 中调用回调函数callback
callback.call(element);
});
}
}
</script>
</body>
事件对象
当事件的响应函数被触发时,会产生一个事件对象event
。浏览器每次都会将这个事件event
作为实参传进之前的响应函数。
这个对象中包含了与当前事件相关的一切信息。比如鼠标的坐标、键盘的哪个按键被按下、鼠标滚轮滚动的方向等。
获取 event 对象(兼容性问题)
所有浏览器都支持event对象,但支持的方式不同。如下。
(1)普通浏览器的写法是 event
。比如:
(2)ie 678 的写法是 window.event
。此时,事件对象 event 是作为window对象的属性保存的。
于是,我们可以采取一种兼容性的写法。如下:
event = event || window.event; // 兼容性写法
代码举例:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script>
//点击页面的任何部分
document.onclick = function (event) {
event = event || window.event; ////兼容性写法
console.log(event);
console.log(event.timeStamp);
console.log(event.bubbles);
console.log(event.button);
console.log(event.pageX);
console.log(event.pageY);
console.log(event.screenX);
console.log(event.screenY);
console.log(event.target);
console.log(event.type);
console.log(event.clientX);
console.log(event.clientY);
}
</script>
</body>
</html>
event 属性
event 有很多属性,比如:
由于pageX 和 pageY的兼容性不好,我们可以这样做:
- 鼠标在页面的位置 = 滚动条滚动的距离 + 可视区域的坐标。
使 div 跟随鼠标移动
代码实现:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
<style type="text/css">
#box1 {
width: 100px;
height: 100px;
background-color: red;
/*
* 开启box1的绝对定位
*/
position: absolute;
}
</style>
<script type="text/javascript">
window.onload = function() {
/*
* 使div可以跟随鼠标移动
*/
//获取box1
var box1 = document.getElementById("box1");
//给整个页面绑定:鼠标移动事件
document.onmousemove = function(event) {
//兼容的方式获取event对象
event = event || window.event;
// 鼠标在页面的位置 = 滚动条滚动的距离 + 可视区域的坐标。
var pagex = event.pageX || scroll().left + event.clientX;
var pagey = event.pageY || scroll().top + event.clientY;
// 设置div的偏移量(相对于整个页面)
// 注意,如果想通过 style.left 来设置属性,一定要给 box1开启绝对定位。
box1.style.left = pagex + "px";
box1.style.top = pagey + "px";
};
};
// scroll 函数封装
function scroll() {
return {
//此函数的返回值是对象
left: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop,
right:
window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft
};
}
</script>
</head>
<body style="height: 1000px;width: 2000px;">
<div id="box1"></div>
</body>
</html>
event.target 获取的是触发事件的标签元素
event.currentTarget 获取到的是发起事件的标签元素
DOM事件流
事件传播的三个阶段是:事件捕获、事件冒泡和目标。
- 事件捕获阶段:事件从祖先元素往子元素查找(DOM树结构),直到捕获到事件目标 target。在这个过程中,默认情况下,事件相应的监听函数是不会被触发的。
- 事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
- 事件冒泡阶段:事件从事件目标 target 开始,从子元素往冒泡祖先元素冒泡,直到页面的最上一级标签。
如下图所示:
PS:这个概念类似于 Android 里的 touch 事件传递。
事件捕获
addEventListener可以捕获事件:
box1.addEventListener("click", function () {
alert("捕获 box3");
}, true);
上面的方法中,参数为true,代表事件在捕获阶段执行。
重点:捕获阶段,事件依次传递的顺序是:window --> document --> html--> body --> 父元素、子元素、目标元素。
这几个元素在事件捕获阶段的完整写法是:
window.addEventListener("click", function () {
alert("捕获 window");
}, true);
document.addEventListener("click", function () {
alert("捕获 document");
}, true);
document.documentElement.addEventListener("click", function () {
alert("捕获 html");
}, true);
document.body.addEventListener("click", function () {
alert("捕获 body");
}, true);
fatherBox.addEventListener("click", function () {
alert("捕获 father");
}, true);
childBox.addEventListener("click", function () {
alert("捕获 child");
}, true);
说明:
(1)第一个接收到事件的对象是 window(有人会说body,有人会说html,这都是错误的)。
(2)JS中涉及到DOM对象时,有两个对象最常用:window、doucument。它们俩是最先获取到事件的。
补充一个知识点:
在 js中:
- 如果想获取
html
节点,方法是document.documentElement
。 - 如果想获取
body
节点,方法是:document.body
。
二者不要混淆了。
事件冒泡
事件冒泡: 当一个元素上的事件被触发的时候(比如说鼠标点击了一个按钮),同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。
通俗来讲,冒泡指的是:子元素的事件被触发时,父元素的同样的事件也会被触发。取消冒泡就是取消这种机制。
冒泡顺序:
一般的浏览器: (除IE6.0之外的浏览器)
- div -> body -> html -> document -> window
IE6.0:
- div -> body -> html -> document
不是所有的事件都能冒泡
以下事件不冒泡:blur、focus、load、unload、onmouseenter、onmouseleave。意思是,事件不会往父元素那里传递。
我们检查一个元素是否会冒泡,可以通过事件的以下参数:
event.bubbles
如果返回值为true,说明该事件会冒泡;反之则相反。
举例:
box1.onclick = function (event) {
alert("冒泡 child");
event = event || window.event;
console.log(event.bubbles); //打印结果:true。说明 onclick 事件是可以冒泡的
}
阻止冒泡
大部分情况下,冒泡都是有益的。当然,如果你想阻止冒泡,也是可以的。可以按下面的方法阻止冒泡。
阻止冒泡的方法
w3c的方法:(火狐、谷歌、IE11)
event.stopPropagation();
IE10以下则是:
event.cancelBubble = true
兼容代码如下:
box3.onclick = function (event) {
alert("child");
//阻止冒泡
event = event || window.event;
if (event && event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
上方代码中,我们对box3进行了阻止冒泡,产生的效果是:事件不会继续传递到 father、grandfather、body了。
事件委托
事件委托,通俗地来讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素。
比如说有一个列表 ul,列表之中有大量的列表项 <a>
标签:
<ul id="parent-list">
<li><a href="javascript:;" class="my_link">超链接一</a></li>
<li><a href="javascript:;" class="my_link">超链接二</a></li>
<li><a href="javascript:;" class="my_link">超链接三</a></li>
</ul>
当我们的鼠标移到<a>
标签上的时候,需要获取此<a>
的相关信息并飘出悬浮窗以显示详细信息,或者当某个<a>
被点击的时候需要触发相应的处理事件。我们通常的写法,是为每个<a>
都绑定类似onMouseOver或者onClick之类的事件监听:
window.onload = function(){
var parentNode = document.getElementById("parent-list");
var aNodes = parentNode.getElementByTagName("a");
for(var i=0, l = aNodes.length; i < l; i++){
aNodes[i].onclick = function() {
console.log('我是超链接 a 的单击相应函数');
}
}
}
但是,上面的做法过于消耗内存和性能。我们希望,只绑定一次事件,即可应用到多个元素上,即使元素是后来添加的。
因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul
上,然后在执行事件函数的时候再去匹配判断目标元素。如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script type="text/javascript">
window.onload = function() {
// 获取父节点,并为它绑定click单击事件。 false 表示事件在冒泡阶段触发(默认)
document.getElementById('parent-list').addEventListener('click', function(event) {
event = event || window.event;
// e.target 表示:触发事件的对象
//如果触发事件的对象是我们期望的元素,则执行否则不执行
if (event.target && event.target.className == 'link') {
// 或者写成 if (event.target && event.target.nodeName.toUpperCase() == 'A') {
console.log('我是ul的单击响应函数');
}
}, false);
};
</script>
</head>
<body>
<ul id="parent-list" style="background-color: #bfa;">
<li>
<p>我是p元素</p>
</li>
<li><a href="javascript:;" class="link">超链接一</a></li>
<li><a href="javascript:;" class="link">超链接二</a></li>
<li><a href="javascript:;" class="link">超链接三</a></li>
</ul>
</body>
上方代码,为父节点注册 click 事件,当子节点被点击的时候,click事件会从子节点开始向父节点冒泡。父节点捕获到事件之后,开始执行方法体里的内容:通过判断 event.target 拿到了被点击的子节点<a>
。从而可以获取到相应的信息,并作处理。
换而言之,参数为false,说明事件是在冒泡阶段触发(子元素向父元素传递事件)。而父节点注册了事件函数,子节点没有注册事件函数,此时,会在父节点中执行函数体里的代码。
总结:事件委托是利用了冒泡机制,减少了事件绑定的次数,减少内存消耗,提高性能。
鼠标的拖拽事件
拖拽的流程:
(1)onmousedown
:当鼠标在被拖拽元素上按下时,开始拖拽;
(2)onmousemove
:当鼠标移动时被拖拽元素跟随鼠标移动;
(3)onmouseup
:当鼠标松开时,被拖拽元素固定在当前位置。
鼠标的滚轮事件
onmousewheel
:鼠标滚轮滚动的事件,会在滚轮滚动时触发。但是火狐不支持该属性。
DOMMouseScroll
:在火狐中需要使用 DOMMouseScroll 来绑定滚动事件。注意该事件需要通过addEventListener()函数来绑定。
键盘事件
事件名
onkeydown
:按键被按下。
onkeyup
:按键被松开。
注意:
- 如果一直按着某一个按键不松手,那么,
onkeydown
事件会一直触发。此时,松开键盘,onkeyup
事件会执行一次。 - 当
onkeydown
连续触发时,第一次和第二次之间会间隔稍微长一点,后续的间隔会非常快。这种设计是为了防止误操作的发生。
键盘事件一般都会绑定给一些可以获取到焦点的对象或者是document。代码举例:
<body>
<script>
document.onkeydown = function(event) {
event = event || window.event;
console.log('qianguyihao 键盘按下了');
};
document.onkeyup = function() {
console.log('qianguyihao 键盘松开了');
};
</script>
<input type="text" />
</body>
判断哪个键盘被按下
可以通过event
事件对象的keyCode
来获取按键的编码。
此外,event
事件对象里面还提供了以下几个属性:
- altKey
- ctrlKey
- shiftKey
上面这三个属性,可以用来判断alt
、ctrl
、和shift
是否被按下。如果按下则返回true,否则返回false。代码举例:
<body>
<script>
document.onkeydown = function(event) {
event = event || window.event;
console.log('qianguyihao:键盘按下了');
// 判断y和ctrl是否同时被按下
if (event.ctrlKey && event.keyCode === 89) {
console.log('ctrl和y都被按下了');
}
};
</script>
</body>
举例:input 文本框中,禁止输入数字。代码实现:
<body>
<input type="text" />
<script>
//获取input
var input = document.getElementsByTagName('input')[0];
input.onkeydown = function(event) {
event = event || window.event;
//console.log('qianguyihao:' + event.keyCode);
//数字 48 - 57
//使文本框中不能输入数字
if (event.keyCode >= 48 && event.keyCode <= 57) {
//在文本框中输入内容,属于onkeydown的默认行为
return false; // 如果在onkeydown中取消了默认行为,则输入的内容,不会出现在文本框中
}
};
</script>
</body>
举例:通过键盘的方向键,移动盒子
代码实现:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
<style type="text/css">
#box1 {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
}
</style>
</head>
<body>
<div id="box1"></div>
<script type="text/javascript">
// 使div可以根据不同的方向键向不同的方向移动
/*
* 按左键,div向左移
* 按右键,div向右移
* ...
*/
//为document绑定一个按键按下的事件
document.onkeydown = function(event) {
event = event || window.event;
//定义一个变量,来表示移动的速度
var speed = 10;
//当用户按了ctrl以后,速度加快
if (event.ctrlKey) {
console.log('smyhvae ctrl');
speed = 20;
}
/*
* 37 左
* 38 上
* 39 右
* 40 下
*/
switch (event.keyCode) {
case 37:
//alert("向左"); left值减小
box1.style.left = box1.offsetLeft - speed + 'px'; // 在初始值的基础之上,减去 speed 大小
break;
case 39:
//alert("向右");
box1.style.left = box1.offsetLeft + speed + 'px';
break;
case 38:
//alert("向上");
box1.style.top = box1.offsetTop - speed + 'px';
break;
case 40:
//alert("向下");
box1.style.top = box1.offsetTop + speed + 'px';
break;
}
};
</script>
</body>
</html>
上方代码,待改进的地方:
(1)移动盒子时,如果要加速,需要先按方向键
,再按Ctrl键
。
(2)首次移动盒子时,动作较慢。后续如果学习了定时器相关的内容,可以再改进。