什么是事件?事件是元素天生自带的一种行为,当我们操作元素的时候,也会把对应的事件触发。而事件绑定是给元素的某个行为绑定一个方法,目的是当事件行为触发时,可以处理一些操作。
常用的事件行为有以下几种:鼠标事件 ,键盘事件,资源事件,焦点事件,触摸事件等等,在了解一些常用的事件前我们要先了解事件的传播机制。
一、事件传播机制
事件流又称为事件传播,DOM2级事件规定的事件流包括三个阶段:捕获阶段(capture phase)、目标阶段(target phase)冒泡阶段(bubbling phase)。
捕获阶段:在事件触发时,会先执行一个阶段叫做“捕获阶段”,从最外层向最里层事件源依次进行查找(从外到内)。可以看出捕获阶段的目的是:为冒泡阶段事先计算好传播的层级路径。
目标阶段:当前元素的相关事件行为触发,如果绑定了方法,则把方法执行
冒泡阶段:触发当前元素的某一个事件行为,不仅仅它的这个行为被触发,而且它所有的的祖先元素相关的事件行为都会被依次的触发(从内到外的顺序)
首先发生的是事件捕获,为获取事件提供机会。然后是实际的目标接收到事件,最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
不论是捕获阶段还是冒泡阶段,在寻找目标元素的过程和向外返回的过程中,所遇到每一个元素身上如果有相同事件的事件处理函数都会被调用。
DOM0事件绑定,绑定的方法都是控制在目标或者是冒泡阶段执行,DOM2可以控制在捕获阶段执行,最后参数true就是控制在捕获阶段执行,只不过一般不用。
<!DOCTYPE html>
<head>
<title>Document</title>
</head>
<body>
<div class="outer">
<div class="inner">
<div class="center">执行</div>
</div>
</div>
</body>
</html>
由上面的结构,可以出事件捕获是从:window ->document->html->body->outer_div->inner_div->center_div
目标阶段:center_div
冒泡阶段:center_div->inner_div_outer_div->body->html->document->window
用下图来作为说明:
二、事件的分类
1、鼠标事件
鼠标事件共11类,包括click,dbclick,contextmenu,mousedown,mouseup,mousemove,mouseover,mouseenter,mouseout,mouseenter,mouseleave,mousewheel。
/*
* 事件名称 详情
* click 点击事件
* dbclick 双击事件
* mousedown 鼠标按下
* mouseup 鼠标抬起
* mousemove 鼠标移动(指针在元素内移动时持续触发)
* mouseover 鼠标经过
* mouseenter 鼠标进入(指针移到有事件监听的元素内)
* mouseout 鼠标离开(指针移出元素,或者移到它的子元素上)
* mouseenter 鼠标进入,类似于mouseover
* mouseleave 鼠标离开(指针移出元素范围外,不冒泡)
* wheel 滚轮向任意方向滚动
* contextmenu 鼠标右键点击
*/
二、键盘事件
/*
* keydown 键盘按下
* keypress 键盘长按(keypress当用户按下键盘上的字符键时触发,按下功能键(ctrl,shift等)时不触发)
* keyup 键盘抬起
*/
3、移动端触摸事件
/*
* touchstart 手指触摸到一个DOM元素时触发
* touchmove 手指在一个DOM元素上滑动时触发
* touchhead 手指从一个DOM元素上移开时触发
* touchcancel 操作取消
*/
4、其它事件
/*
* 其它事件
* error 资源加载失败时
* load 资源加载完
* focus 元素获得焦点
* blur 元素失去焦点
* reset 点击重置按键
* submit 点击提交按钮
* change 内容改变
* scroll 滚动条
* resize 大小改变
* gesturestart
* gesturechange/gestureupdate
* canplay
* canplaythrough
* readystatechange AJAX 请求状态改变事件
* ......
*/
二、事件绑定
事件绑定:给元素的某个事件行为绑定方法,这样事件行为触发的时候,对应绑定的方法会执行,完成一些需要完成的功能
1、DOM0级事件绑定
元素.on事件行为 = function(){};
原理:事件绑定的原理就是给当前元素对象的某些私有属性赋值一个函数,当事件行为触发,浏览器会帮助把绑定的方法执行。
注意:on只能给元素添加一个事件,如果说有多个on,那后面的会把前面的进行覆盖。
不足:如果元素私有属性中不具备某个事件的私有属性则无法给这些事件绑定方法
事件移除:元素.on事件行为 = null;
2.DOM2级事件绑定
【标准浏览器】
元素.addEventListener(事件行为,function(){},true/false);
参数解析:参数1:事件行为,不加on,它是一个字符串;
参数2:事件绑定,这个事件绑定函数可以是匿名也可以是命名(真实项目中最好命名);
参数3:布尔值:true代表事件是在捕获阶段发生,falses代表事件在冒泡阶段发生。
原理:基于元素的原型链找到EventTarget.prototype,使用内置的方法进行事件绑定和移除。它是基于浏览器内置的事件池机制完成事件监听和方法的绑定。
注意:可以给当前元素的某个事件类型绑定多个不同的方法,事件触发会按照绑定的顺序把方法依次执行。
基于addEventListener向事件池增加方法,存在去重的机制“同一个元素,同一个事件类型,在事件池中中只能存储一遍这个方法,不能重复的存储”。
事件移除:元素.removeEventListener(事件行为,function(){},true/false);需要注意的是不能移除addEventListener添加的匿名函数。只能称除同一阶段的绑定函数(布尔值相同)。
【IE6-8】
元素.attachEvent(’onxxxx‘,function(){});
事件移除:detachEvent(event,function(){});
三、事件对象及常用属性
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。所有浏览器都支持event对象。
事件对象和函数以及给谁绑定的事件没有必然的关系,它存储的是当前本次操作的相关信息,操作一次只能有一份信息,第二次存储的信息会把上一次操作存储的信息替换。
1. 普通事件对象
普通事件对象:事件对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都有些共有的属性和方法。常用的有type(事件类型),target(事件源),path,srcElement,which, ev.stopPropagation(),ev.preventDefault() ......。
<script>
window.onload = function (ev) {
console.log(ev);
//Event 普通事件对象
}
</script>
2. 鼠标事件对象
鼠标事件对象:如果是鼠标操作,获取的是MouseEvent类的实例,我们把其叫做鼠标事件对象。鼠标事件对象-> MouseEvent.prototype->UIEvent.prototype->Event.prototype->Object.prototype
常用的属性和方法有:
clientX/clientY: 当前鼠标触发点距离当前窗口左上角的x/y轴坐标;
pageX/pageY:当前鼠标触发点距离当前页面左上角x/y轴坐标,它与clientX/clientY的区别是它们不随滚动条的位置变化;
screenX/screenY:表示鼠标指针相对于屏幕的水平和垂直坐标;
offsetX/offsetY: 表示相对于定位父级的水平和垂直坐标当页面无定位元素时,body是元素的定位父级。由于body的默认margin是8px,所以offsetX/Y与clientX/Y差(8,8)。
在鼠标事件中,除了mouseenter和mouseleave事件外,所有的鼠标都是会冒泡的。(也可理解为mouseenter/mouseleave不会把事件传递给子元素)
当鼠标移入时,会依次的触发mousemove事件和mouseenter事件,再触发mousemove事件。
当鼠标移出时,触发mousemove,mouseout,mouseleave事件.
双击鼠标时触发mousedown,mouseup,click,mousedown,mouseup,click,dbclick事件
点击鼠标右键触发mousedown,mouseup,contextmenu事件
嵌套元素的移入移出时:
从父级元素进入子级元素时,顺序为:父级元素的mouseout、子级元素的mouseover、父级元素的mouseover、子级元素的mouseenter
从子级元素进入父级元素时,顺序为:子级元素的mouseout、父级元素的mouseout、子级元素的mouseleave、父级元素的mouseover
<div>点击</div>
<script>
let div = document.querySelector('div');
div.onclick=function(ev){
console.log(ev);
}
</script>
示例:鼠标点击出现小圆圈效果
html&css部分
<style>
div {
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid rgb(146, 145, 145);
position: absolute;
left: 0;
top: 0;
display: none;
}
</style>
<body>
<div></div>
<div></div>
<div></div>
</body>
js部分
<script>
let divs = document.querySelectorAll("div");
//创建一个move函数,obj代表你要运动的哪个div,ev代表事件对象
function move(obj, ev) {
obj.style.display = 'block';
var opc = 100;
var timer = setInterval(function () {
//宽高慢慢变大
obj.style.width = obj.offsetWidth + 5 + 'px';
obj.style.height = obj.offsetHeight + 5 + 'px';
//每走一次透明度要变化
obj.style.opacity = (opc -= 10) / 100;
obj.style.left = ev.clientX - obj.offsetWidth / 2 + 'px';
obj.style.top = ev.clientY - obj.offsetHeight / 2 + 'px';
//清除定时器
if (opc <= 0) {
clearInterval(timer);
//在定时器停的时候,把它们的值都回到最初的状态,为了下一次点击还从最初的状态开始运动
obj.style.width = '10px';
obj.style.height = '10px';
obj.style.opacity = 0;
}
}, 17);
}
//调用move函数
document.onclick = function (ev) {
//走第一个圈
move(divs[0], ev);
//走第二个圈
setTimeout(function () {
move(divs[1], ev);
}, 500);
// 走第三个圈
setTimeout(function () {
move(divs[2], ev);
}, 1000);
}
</script>
3. 键盘事件对象
键盘事件对象:如果是键盘操作,获取的是keyboardEvent类的实例.。我们叫其键盘事件对象。键盘事件对象-> KeyboardEvent.prototype->UIEvent.prototype->Event.prototype->Object.prototype
常用的键盘码:左 上 右 下:37 38 39 40 enter: 13 backspace: 8 del: 46 ctrl :17 shift:16 space:32
常用的属性:event.keyCode、eventctrlKey、eventshiftKey......
常用的事件:keydown、keypress、keyup
keydown:当用户按下键盘上的任意键时触发,如果按住不放的话,会重复触发该事件
keypress:当用户按下键盘上的字符键时触发,按下功能键时不触发。如果按住不放的话,会重复触发该事件
keyup:当用户释放键盘上的键时触发
示例:身份证18位
<input type="text" id="cardID">
<script>
let cardID = document.querySelector('input');
cardID.onkeydown = cardID.onkeyup = function (ev) {
// console.log(ev.keyCode);
let val = this.value,
reg = /[^0-9X]/g;
this.value = val.replace(reg, '');
//超过18位,禁止
if (this.value.length >= 18) {
let arr = [8, 13, 37, 38, 39, 40, 46]
if (!arr.includes(ev.keyCode)) {
ev.preventDefault();
}
}
//按enter弹出
if (ev.keyCode === 13) {
alert(this.value);
}
}
</script>
阻止默认行为: 阻止默认行为需要知道这些默认行为什么时候发生。普遍的有两种方式:return false、preventDefault().
<a id ="link" href="www.baidu.com">阻止跳转</a>
<script>
/*
link.onclick=function(ev){
//返回一个false,相当于结束后面即将执行的步骤
return false;
}
*/
/*
link.onclick = function(ev){
ev.preventDefault();
}
*/
</script>
4. 手指事件对象
常见的属性有touch/changedTouches: touchs只能获取手指在屏幕上的时候的信息,如果手指离开了屏幕,信息就没有了。而changedTouchs相对于touchs来说可以获取到手指离开前的信息,所以项目中用changedTouches最多。
<script> btn.ontouchstart = function (ev) { console.log(ev); //TouchEvent事件 //获取的是第一个手指信息,存储了clientX等基本信息 let point = ev.changedTouches[0]; } </script>
四、mousseout与mouseover的区别
<style>
.outer{
margin:200px auto;width: 400px;height: 400px;background-color: lightgreen;}
.inner{
margin:200px auto;width: 300px;height: 300px;background-color: lightblue;
}
.center{margin:200px auto;width: 200px;height: 200px;background-color: lightpink;
}
</style>
<body>
<div class="outer">
<div class="inner">
<div class="center"></div>
</div>
</div>
</body>
如上代码所示,我们可以看到下面图片的分析,从分析中我们可以出:
mouseover本身不是进入,而是看鼠标在哪个元素上面,从子元素进入父元素,触发父元素的mouseover,从父元素进入子元素触发父元素的mouseout
mouseleave默认阻止了事件的冒泡传播,从父元素进入子元素,从子元素重新进入父元素,父元素的mouseenter和mouseleave都不针对触发。
所以盒子中如果有后代元素,我们尽量用mouseenter,但是需要冒泡传播做的事情,还是需要用mouseover。
五、放大镜
仿照淘宝页面做了一个放大镜效果
html部分
<body>
<section class="magnifier clearfix">
<!-- 左侧小图 -->
<div class="abbre">
<img src="./img/dog.jpg" alt="" />
<div class="mark"></div>
</div>
<!-- 右侧大图 -->
<div class="origin">
<img src="./img/dog.jpg" alt="" />
</div>
</section>
</body>
css部分
<link rel="stylesheet" href="css/reset.min.css" />
<style>
.magnifier {box-sizing: border-box;margin: 20px auto; 650px;}
.magnifier .abbre,.magnifier .origin {float: left;}
.magnifier .abbre {position: relative;box-sizing: border-box; 300px; height: 300px;}
.magnifier .abbre img { 100%; height: 100%; }
.magnifier .abbre .mark { display: none;position: absolute;top: 0;left: 0;
100px;height: 100px; background: rgba(255, 0, 0, 0.2);cursor: move;}
.magnifier .origin {display: none;position: relative;box-sizing: border-box;
350px;height: 350px;overflow: hidden;}
.magnifier .origin img {position: absolute;top: 0;left: 0;}
</style>
js部分
<script src="../../node_modules/jquery/dist/jquery.min.js"></script>
<script>
let $abbre = $(".abbre"),
$mark = $abbre.find(".mark"),
$origin = $(".origin"),
$originImg = $origin.find("img");
//计算mark盒子的位置
let abbreW = $abbre.outerWidth(),
abbreH = $abbre.outerHeight(),
abbreOffset = $abbre.offset(),
markW = $mark.outerWidth(),
markH = $mark.outerHeight(),
originW = $origin.outerWidth(),
originH = $origin.outerHeight(),
originImgW = (abbreW / markW) * originW,
originImgH = (abbreH / markH) * originH;
//计算出大图的大小
$originImg.css({
originImgW,
height: originImgH,
});
function computedMark(ev) {
//2.计算mark的位置
let markL = ev.pageX - abbreOffset.left - markW / 2,
markT = ev.pageY - abbreOffset.top - markH / 2;
let minL = 0,
minT = 0,
maxL = abbreW - markW,
maxT = abbreH - markH;
markL = markL < minL ? minL : markL > maxL ? maxL : markL;
markT = markT < minT ? minT : markT > maxT ? maxT : markT;
$mark.css({
left: markL,
top: markT,
});
//3.控制大图移动的距离
$originImg.css({
left: (-markL / abbreW) * originImgW,
top: (-markT / abbreH) * originImgH,
});
}
$abbre
.mouseenter(function (ev) {
$mark.css("display", "block");
$origin.css("display", "block");
computedMark(ev);
})
.mouseleave(function (ev) {
$mark.css("display", "none");
$origin.css("display", "none");
})
.mousemove(function (ev) {
computedMark(ev);
});
</script>