珠峰培训

JAVASCRIPT中的事件基础及核心原理以及项目实战

作者:

2017-12-05 12:30:57

275

事件基础:全新认识事件

1、什么是事件?
事件是元素天生具备的行为方式(和写不写JS代码没关系),当我们去操作元素的时候会触发元素的很多事件

2、事件绑定
给当前元素的某一个事件绑定方法,目的是为了让当前元素某个事件被触发的时候,可以做一些事情

给某一个事件绑定方法,目前常用的有两种方式:
1、DOM0级事件绑定
oBox.onclick=function(){}

2、DOM2级事件绑定
oBox.addEventListener('click',function(){},false) 标准浏览器
oBox.attachEvent('onclick',function(){}) IE6~8

3、常用的事件汇总
[PC端]
表单元素常用的事件行为
blur:失去焦点
focus:获取焦点
change:内容改变
select:被选中事件

键盘常用事件行为
keydown:键盘按下
keyup:键盘抬起
keypress:键盘按下后有keypress(中文输入法状态下,会触发keydown,但是由于内容没有放在文本框中keypress没有被触发)

鼠标常用事件行为
click:点击(不是单击)
dblclick:双击(300MS内连续触发两次点击事件,这样为双击事件)
mouseover:鼠标滑过
mouseout:鼠标离开
mouseenter:鼠标进入
mouseleave:鼠标离开
mousemove:鼠标移动
mousedown:鼠标左键按下
mouseup:鼠标左键抬起
mousewheel:鼠标滚轮滚动

其它常用的事件行为
load:加载成功
error:加载失败
scroll:滚轮滚动事件
resize:大小改变事件 window.onresize当浏览器窗口的大小发生改变触发这个事件

[移动端]
移动端的键盘一般都是虚拟键盘,虽然部分手机存在keydown/keyup但是兼容不好,所以我们想用键盘事件的时候,使用input事件代替
inputBox.oninput=function(){}

移动端没有鼠标,所以鼠标类的事件在移动端兼容都特别的差(mousexxx这些事件不要想着在移动端用了)

移动端的大部分操作是靠手指完成的,移动端独有手指事件
单手指事件模型:touchstart、touchmove、touchend、touchcancel…
多手指事件模型:gesturestart、gesturechange、gestureend…

移动端还有很多操作是基于手机硬件完成的,例如:传感器、陀螺仪、重力感应器等

在移动端兼容click事件,PC端的click是点击,但是移动端把click事件当做单击:移动端使用click事件处理点击操作存在300MS延迟

事件对象

//=>事件绑定:给oBox的click事件,基于DOM0级事件绑定的方式,绑定了一个方法;以后当我们手动触发oBox的click行为的时候,会把绑定的方法执行;
oBox.onclick = function (e) {
//=>arguments[0] === e:当方法执行的时候,浏览器默认传递给方法的参数值(事件对象)
};

当元素的某一个事件行为被触发,不仅会把之前绑定的方法执行,而且还会给当前绑定的方法传递一个值(浏览器默认传递的), 我们把传递的这个值称为 事件对象
1、因为这个值是个对象类型的值,里面存储了很多的属性和方法
2、这个对象中存储的值都是当前操作的一些基本信息,例如:鼠标的位置、触发的行为类型、触发的事件源等

以上所说都是针对于标准浏览器,IE6~8下不是这样的机制
IE6~8方法被触发执行的时候,浏览器并没有把事件对象当做值传递给函数(e在IE6~8下是undefined);但是IE6~8也有事件对象,事件对象需要我们通过window.event单独获取;

oBox.onclick = function (e) {
//=>以后想要获取事件对象,我们最好写两套处理,先验证是否传递E,没传递到WINDOW上去找即可
e = e || window.event;
}

事件对象是为了记录当前本次操作基本信息的,所以只和本次操作有关:本次操作,页面中不管通过什么方式获取的e或者window.event(也不管在哪获取的),他们存储的基本信息应该是相同的

鼠标事件对象 MouseEvent

clientX / clientY:当前鼠标触发点距离当前窗口左上角的X/Y轴坐标

pageX / pageY:当前鼠标触发点距离BODY左上角的X/Y轴坐标(页面第一屏幕左上角),但是IE6~8中没有这两个属性

type:当前触发事件的类型

target:事件源(当前鼠标操作的是哪一个元素,那么事件源就是谁),IE6~8下没有target这个属性,它有srcElement这个属性代表事件源

preventDefault:此方法是为了阻止事件的默认行为,IE6~8下没有这个方法,需要使用e.returnValue=false来处理

stopPropagation:此方法是为了阻止事件的冒泡传播,IE6~8不兼容,需要使用e.cancelBubble=true处理

oBox.onclick=function(e){
if(typeof e==='undefined'){
//=>IE6~8
e = window.event;
//=>pageX / pageY
e.pageX=e.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft);
e.pageY=e.clientY+(document.documentElement.scrollTop||document.body.scrollTop);
//=>target
e.target=e.srcElement;
//=>preventDefault
e.preventDefault = function(){
e.returnValue=false;
}
//=>stopPropagation
e.stopPropagation=function(){
e.cancelBubble=true;
}
}
//=>下面在使用属性或者方法的时候,完全按照标准浏览器的语法去实现即可(IE6~8下不兼容的属性和方法我们已经重写为兼容的了)
}

上面的兼容处理方式属于比较完整的,但是如果项目中我们只想用到一个不兼容的属性,我们没必要写这么多,简单处理一下兼容就可以

oBox.onclick=function(e){
e=e||window.event;
e.target=e.target||e.srcElement;
e.preventDefault?e.preventDefault():e.returnValue=false;
}

键盘事件对象 KeyboardEvent

code :当前键盘的按键,例如:按删除键,存储的是’Backspace’ (IE6~8下没有这个属性) ,还有一个叫做key的属性和code一样,存储的也是按键的名称

keyCode:存储的是当前键盘按键对应的码值(大部分按键都有自己的码值)

which:和keyCode一样对应的也是键盘码的值(它不兼容IE6~8)

Alt text

[windows]

Alt text

[MAC]

inputBox.onkeyup=function(e){
e=e||window.event;
var code=e.which||e.keyCode;
}

应用键盘事件对象,完成推箱子的效果

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>珠峰培训</title>
<link rel="stylesheet" href="css/reset.min.css">
<style>
html, body {
height: 100%;
overflow: hidden;
background: lightcyan;
}
.box {
position: absolute;
top: 100px;
left: 200px;
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<script src="js/jquery-1.11.3.min.js"></script>
<script>
var $box = $('#box');
var minL = 0,
minT = 0,
maxL = (document.documentElement.clientWidth || document.body.clientWidth) - $box[0].offsetWidth,
maxT = (document.documentElement.clientHeight || document.body.clientHeight) - $box[0].offsetHeight;
$(document).on('keydown', function (e) {
//=>JQ中的事件绑定已经把E的兼容处理都完成了,我们只需要按照标准浏览器中的写法操作即可
var code = e.which,
curL = parseFloat($box.css('left')),
curT = parseFloat($box.css('top'));//=>JQ中CSS方法获取的结果是默认不去单位的(我们需要自己去除单位)
switch (code) {
case 37:
curL -= 10;
break;
case 39:
curL += 10;
break;
case 38:
curT -= 10;
break;
case 40:
curT += 10;
break;
}
//->在设置样式前,我们需要做一下边界判断
curL = curL < minL ? minL : (curL > maxL ? maxL : curL);
curT = curT < minT ? minT : (curT > maxT ? maxT : curT);
$box.css({
left: curL,
top: curT
});
//=>按下空格键,让元素蹦一下
if (code === 32) {
$box.stop().animate({top: curT - 60}, 500, function () {
$box.stop().animate({top: curT}, 500);
});
}
});
</script>
</body>
</html>

移动端手指事件对象 TouchEvent

touches & changedTouches & targetTouches:存储的是当前屏幕上每一个手指操作的位置信息

touches:只有手指在屏幕上我们才可以获取对应的信息值(手机离开屏幕没有相关信息了,这样touchend事件中我们无法通过touches获取手指信息)

changedTouches:手指在屏幕上的时候,和touches获取的信息一样,但是它可以记录手指离开屏幕一瞬间所在的位置信息(最常用的)

TouchEvent
type:'touchstart',
target:事件源,
touches:
0:{
clientX:xxx,
clientY:xxx,
pageX:xxx,
pageY:xxx
...
}
...
length:1
changeTouches以及targetTouches存储的结构和touches相同
...

我们知道移动端的click是单击事件(不是PC端的点击效果),存在300ms的延迟,项目中我们需要解决这个延迟:
使用touchstart、touchmove、touchend来处理

简单处理

oBox.ontouchend=function(){
//=>不管你是怎么操作的,我只需要知道,手指离开就算点击即可,存在一些问题:
//=>手指按住屏幕不松开,时间超过750ms应该算作长按,不是点击,手指离开不应该按照点击处理
//=>手指在屏幕上滑动了,此时应该算作滑动不是点击,手指离开屏幕也不应该按照点击处理
//...
}

详细处理一下

let oBox = document.querySelector('#box');
oBox.ontouchstart = function (e) {
let point = e.changedTouches[0];
//=>记录当前手指的起始坐标位置(记录在当前元素的自定义属性上:在其它方法中如果我们想要获取的话,直接通过自定义属性获取即可)
this.strX = point.pageX;
this.strY = point.pageY;
this.isMove = false;
};
oBox.ontouchmove = function (e) {
let point = e.changedTouches[0];
//=>一般我们手指操作,都会或多或少的发生一些偏移(习惯性偏移),此时不应该算作滑动,只有滑动的距离超出一定范围,我们按照滑动处理即可(一般都是把10px作为偏差值)
let changeX = point.pageX - this.strX,
changeY = point.pageY - this.strY;
this.changeX = changeX;
this.changeY = changeY;
if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10) {
this.isMove = true;
}
};
oBox.ontouchend = function (e) {
let point = e.changedTouches[0];
//=>手指离开的时候:验证是否发生滑动
if (!this.isMove) {
//=>点击操作
console.log('我是点击操作~~');
return;
}
//=>滑动操作
let dir = null;
if (Math.abs(this.changeX) > Math.abs(this.changeY)) {
//=>左右滑动
dir = this.changeX < 0 ? 'LEFT' : 'RIGHT';
} else {
//=>上下滑动
dir = this.changeY < 0 ? 'UP' : 'DOWN';
}
console.log(`当前手指滑动的方向为:${dir}`);
};

在移动端开发中,我们需要的一些操作(例如:点击、单击、双击、长按、滑动[四方向]…)都是基于内置原生的 touchstart\touchmove\touchend 事件一点点模拟出来的效果,没有现成的事件
而多手指操作(例如:旋转、缩放…)都是基于gesture事件模型模拟出来的效果

目前市场上有很多成熟的类库或者插件,专门为大家把常用的操作进行了封装,我们直接调取使用即可
1、fastclick.js:目的就是为了解决移动端click事件300ms延迟的问题(如果我们的移动端使用了click事件,我们只需要把这个JS导入配置一下即可)
2、百度云touch手势事件库
3、hammer.js
4、zepto.js:提供移动端事件操作的板块,也是目前市场上使用率最高的(小型JQ)

$('.box').tap(function(){
//=>点击
});
$('.box').singleTap(function(){
//=>单击
});
$('.box').doubleTap(function(){
//=>双击
});
$('.box').longTap(function(){
//=>长按
});
$('.box').swipe(function(){
//=>滑动 .swipeLeft/.swipeRight/.swipeUp/.swipeDown
});
//=> .pinchIn(function(){}) 缩小
//=> .pinchOut(function(){}) 放大
//...

A标签的默认行为及阻止

A标签都有哪些默认行为
1、超链接:点击A标签可以实现页面的跳转
2、锚点定位:通过HASH(哈希)值定位到当前页面指定ID元素的位置

真实项目中我们想用A标签做一个普通的按钮(优势:它的:hover样式是兼容所有浏览器的),此时就要把之前提到的两个默认行为阻止掉才可以

阻止A标签的默认行为

//=>在HTML中阻止默认行为(最常用)
<a href='javascript:;'></a>
<a href='javascript:void 0;'></a>
/*
* <a href='http://www.zhufengpeixun.cn' id='link'></a>
*/
//=>当点击A标签的时候
//1、首先会触发click事件
//2、其次按照href中的地址进行页面跳转
link.onclick=function(){
return false;//=>在函数中返回一个FALSE(只能是FALSE)也可以阻止默认行为
}
link.onclick=function(e){
//=>通过事件对象阻止默认行为
e=e||window.event;
e.preventDefault?e.preventDefault():e.returnValue=false;
}

事件的传播机制

事件传播有三个阶段

Event.prototype

  • 0 NONE:默认值,不代表任何的意思
  • 1 CAPTURING_PHASE:捕获阶段
  • 2 AT_TARGET:目标阶段(当前事件源)
  • 3 BUBBLING_PHASE:冒泡阶段

三个阶段处理的事情

Alt text

当前元素的某个事件行为被触发,它所有祖先元素(一直到document)的相关事件行为也会被依次触发(顺序:从内向外),如果祖先祖先元素的这个行为绑定了方法,绑定的方法也会被触发执行,我们把事件的这种传播机制叫做:**冒泡传播**

mouseover和mouseenter的区别

mouseover:鼠标滑到元素上
mouseenter:鼠标进入元素里面

[1]
mouseover 存在事件的冒泡传播机制,而mouseenter 浏览器把它的事件冒泡传播机制阻止了

[2]
鼠标从父元素进入到子元素
over:先触发父元素的mouseout(因为鼠标已经不再父元素上了,mouseover本意是鼠标在元素上才算触发),在触发子元素的mouseover(由于冒泡传播机制导致父元素的mouseover也被重新触发了)

enter:进入,从大盒子进入到小盒子,没有触发大盒子的mouseleave事件,但是也触发了小盒子的mouseenter,浏览器阻止了它的冒泡传播,所以大盒子的mouseenter不会被触发

事件委托/事件代理

利用事件的冒泡传播机制完成(mouseenter不存在委托机制,因为它不存在冒泡传播)

一个容器中很多元素的同一个事件行为都要绑定方法,此时我们没有必要在获取所有元素,一个个的绑定方法了,我们只需要给最外层元素的这个事件绑定一个方法,这样不管里面哪一个元素的这个事件行为被触发,都会利用冒泡传播机制,把外层容器绑定的那个方法执行,在方法执行的时候,我们可以根据事件源判断出操作的是哪个元素,从而做不同的事情(使用事件委托这样完成的操作比一个个的单独事件绑定性能提高50%左右)

视频观看地址:http://www.html5train.com/kecheng/detail_1029018