事件:是元素天生自带的默认操作行为,不论我们是否给其绑定了方法,当我们操作的时候,也会把对应的事件触发;
事件绑定:是给元素的某个行为绑定一个方法,目的是当事件行为触发的时候,可以做一些事情
# 一、浏览器常用的事件行为
# 思维导图
# 1、鼠标事件
鼠标点击:鼠标按下弹起算一次点击
- onclick:点击(移动端click被识别为单击)
- ondblclick:双击(大概是在 300ms 之间点击两次)
- oncontextmenu:右键点击
鼠标按下:不分左右键或者滚轮,只要按下/抬起就会触发
- onmousedown:鼠标按下
- onmouseup:鼠标抬起
鼠标滚动
- onmousewhell:鼠标滚轮滚动
鼠标移动:鼠标尖端移动触发
- onmousemove:鼠标移动
鼠标经过
- onmouseout:鼠标滑出
- onmouseover:鼠标滑过(在表面经过即可)
鼠标进入
- onmouseenter:鼠标进入(进入到里面)
- onmouseleave:鼠标离开
# 思考:onmouseover 和 onmouseenter 的区别
- mouseover/mouseout 存在冒泡机制。划过和划出(鼠标在谁身上,相当于划过谁)
- mouseenter/mouseleave 不存在冒泡传播机制。进入和离开。
如何选用:项目中,如果一个容器中有后代元素,想要鼠标进入和离开做啥事,我们一般都用mouseenter和mouseleave。
# 2、键盘事件
能绑定键盘事件的有:
input
、textarea
、window
、document.body
等
- 如果想给不能编辑的元素绑定键盘事件,需要给这个元素加一个:
contenteditable = "true"
- onkeydown:按下某个键
- onkeyup:抬起某个键
- onkeypress:除Shift/Fn/CapsLock键以外,其它键按住(连续触发)
# 3、表单事件
使用范围:input ......
- onfocus:获取焦点(光标进入input时,触发事件)
- onblur:失去焦点(光标离开input时,触发事件)
- oninput:内容改变(只要内容发生改变就会触发)
- onchange:内容改变(并且失焦的时候才会触发)
# 4、音视频事件
使用范围:音频、视频
- canplay:可以播放(资源没有加载完,播放中可能会卡顿)
- canplaythrough:可以播放(资源已经加载完,播放中不会卡顿)
- play:开始播放
- playing:播放中
- pause:暂停播放
# 5、系统事件
其它常用事件
- window.onscroll:页面滚动
- window.onresize:页面大小发生改变的时候触发
- window.onload:页面资源加载完毕之后触发
- img.onload :图片加载完成
- window.onbeforeunload:当前页面关闭之前
- window.onerror:资源加载失败
# 6、移动端事件
单手指事件模型 Touch
- ontouchstart:手指碰到屏幕(手指按下)
- ontouchmove:手指在屏幕上移动
- ontouchend:手指离开屏幕(手指松开)
- ontouchcancel:操作取消(一般应用于非正常状态下操作结束)
多手指事件模型 Gesture
- ongesturestart:手指碰到屏幕(手指按下)
- ongesturechange / ongestureupdate:手指在屏幕上移动
- ongestureend:手指离开屏幕(手指松开)
- ongesturecancel:操作取消(一般应用于非正常状态下操作结束)
# 7、其他事件
- transitionend:动画过渡完成
- onreadystatechange:AJAX请求状态改变事件
- ......
事件行为还有很多,这里我们暂时列举这些;更多的内容可以参考 MDN,事件参考 (opens new window); 或者可以查看元素的属性(属性中onxxx就是元素拥有的事件行为)
# 二、DOM0 和 DOM2 事件绑定
# 思维导图
# 1、DOM0 事件绑定
- 语法:元素.on事件行为=function(){}
- 原理:给元素的私有属性赋值,当事件触发,浏览器会帮我们把赋的值执行,但是这样也导致 “只能给当前元素某一个事件行为绑定一个方法”
box.onclick = function () {
console.log('哈哈哈~~');
}
box.onclick = function () {
console.log('呵呵呵~~');
}
只输出后面的:
- 移除
box.onclick = function () {
console.log('哈哈哈~~');
//=>移除事件绑定:DOM0直接赋值为null即可
box.onclick = null;
}
# 2、DOM2 事件绑定
- 语法:
- 元素.addEventListener(事件行为,function(){},true/false)
- true/false 可以省略,默认是false
- IE6~8中:元素.attachEvent('on事件行为',function(){})
- 元素.addEventListener(事件行为,function(){},true/false)
- 原理:
- 基于原型链查找机制,找到EventTarget.prototype上的方法并且执行,此方法执行,会把给当前元素某个事件行为绑定的所有方法,存放到浏览器默认的事件池中(绑定几个方法,会向事件池存储几个);
- 当事件行为触发,会把事件池中存储的对应方法,依次按照顺序执行 “给当前元素某一个事件行为绑定多个不同方法”
事件池特点:
- 基于addEventListener向事件池增加方法,存在去重的机制 “同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”
box.addEventListener('click', function () {
console.log('哈哈哈~~');
}, false);
box.addEventListener('click', function () {
console.log('呵呵呵~~');
}, false);
两个都能输出:
- 绑定注意点:DOM2事件绑定的时候,我们一般都采用实名函数
- 目的:这样可以基于实名函数去移除事件绑定
- 语法:box.addEventListener('click', fn, false);
function fn1(){ console.log(1); }
function fn2(){ console.log(2); }
function fn3(){ console.log(3); }
box.addEventListener('click', fn2, false);
box.addEventListener('click', fn3, false);
box.addEventListener('click', fn1, false);
//=>基于addEventListener向事件池增加方法,存在去重的机制 “同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”
box.addEventListener('click', fn1, false); // 所以此步跳过,不再存储
box.addEventListener('mouseover', fn1, false);
- 移除绑定:从事件池中移除,所以需要指定好事件类型、方法等信息(要和绑定的时候一样才可以移除)
- 语法:box.removeEventListener('click', fn, false)
function fn() {
console.log('哈哈哈~~');
//=>移除事件绑定:从事件池中移除,所以需要指定好事件类型、方法等信息(要和绑定的时候一样才可以移除)
box.removeEventListener('click', fn, false);
}
box.addEventListener('click', fn, false);
# 3、特别注意的几点
- -1)DOM0和DOM2可以混在一起用:执行的顺序以绑定的顺序为主
box.addEventListener('click', function () {
console.log('哔咔哔咔~~');
});
box.onclick = function () {
console.log('哇咔咔~~');
}
box.addEventListener('click', function () {
console.log('call~~');
});
- -2)DOM0 比 DOM2 快
- -3)DOM0中能做事件绑定的事件行为,DOM2都支持;DOM2里面一些事件,DOM0不一定能处理绑定,例如:transitionend、DOMContentLoaded...
box.style.transition = 'opacity 1s';
box.ontransitionend = function () {
console.log('哇咔咔~~');
}
box.addEventListener('transitionend', function () {
console.log('哇咔咔~~');
});
window.addEventListener('load', function () {
//=>所有资源都加载完成触发
console.log('LOAD');
});
window.addEventListener('DOMContentLoaded', function () {
//=>只要DOM结构加载完就会触发
console.log('DOMContentLoaded');
});
//=>$(document).ready(function(){})
$(function () {
//=>JQ中的这个处理(DOM结构加载完触发)采用的就是DOMContentLoaded事件,并且依托DOM2事件绑定来处理,所以同一个页面中,此操作可以被使用多次
});
/* JQ中的事件绑定采用的都是DOM2事件绑定,例如:on/off/one */
# 4、window.onload
和 $(document).ready()
的区别
- -1)$(document).ready()
- 采用的是DOM2事件绑定,监听的是
DOMContentLoaded
这个事件,所以只要DOM结构加载完成就会被触发执行, - 而且同一个页面中可以使用多次(绑定不同的方法,因为基于DOM2事件池绑定机制完成的)
- 采用的是DOM2事件绑定,监听的是
- -2)window.onload
- 必须等待所有资源都加载完成才会被触发执行,采用DOM0事件绑定,同一个页面只能绑定一次(一个方法),
- 想绑定多个也需要改为
window.addEventListener('load', function () {})
DOM2绑定方式
# 5、DOM0 和 DOM2 的传播的区别
- DOM0 绑定的方法,只能在目标阶段和冒泡阶段触发执行
- DOM2绑定的方法,我们可以控制在捕获阶段执行
- 元素.addEventListener(事件行为,function(){},true/false)
- 第三个参数:不写 默认是 false
- false:代表在冒泡阶段执行此方法
- true:代表在捕获阶段执行此方法(基本没用过)
# 三、事件对象
# 思维导图
# 1、定义:
- 给元素的事件行为绑定方法,当事件行为触发方法会被执行,不仅被执行,而且还会把当前操作的相关信息传递给这个函数 =>传递过来相关信息就叫做事件对象
事件对象是由事件当前本身产生的,和执行什么函数没有关系
# 2、原理:
事件对象和函数以及给谁绑定的事件没啥必然关系,它存储的是当前本次操作的相关信息,操作一次只能有一份信息,所以在哪个方法中获取的信息都是一样的;第二次操作,存储的信息会把上一次操作存储的信息替换掉...;
- 每一次事件触发,浏览器都会这样处理一下:
- 1.捕获到当前操作的行为(把操作信息获取到),通过创建MouseEvent等类的实例,得到事件对象EV
- 2.通知所有绑定的方法(符合执行条件的)开始执行,并且把EV当做实参传递给每个方法,所以在每个方法中得到的事件对象其实是一个
- 3.后面再重新触发这个事件行为,会重新获取本次操作的信息,用新的信息替换老的信息,然后继续之前的步骤...
# 3、事件对象类型
# -1)鼠标事件对象
如果是鼠标操作,获取的是MouseEvent类的实例(这个实例就是 =>鼠标事件对象)
box.onclick = function (ev) {
console.log(ev);
}
- 原型链:
- 鼠标事件对象 -> MouseEvent.prototype -> UIEvent.prototype -> Event.prototype -> Object.prototype
- 常用属性:
- clientX/clientY:当前鼠标触发点距离当前窗口左上角的X/Y轴坐标
- pageX/pageY:触发点距离当前页面左上角的X/Y轴坐标
# -2)键盘事件对象
如果是键盘操作,获取的是KeyboardEvent类的实例 =>键盘事件对象
- 常用属性:
- code & key:存储的都是按键,code更细致
- keyCode & which:存储的是键盘按键对应的码值
- 键盘常用码值:
- 方向键:37 38 39 40 =>左上右下
- 空格SPACE:32
- 回车ENTER:13
- 回退BACK:8
- 删除DEL:46
- SHIFT:16
- CTRL:17
- ALT:18
window键盘码值:
苹果键盘码值:
# -2)除了以上还有:
- 普通事件对象(Event)、
- 手指事件对象(TouchEvent)等
这里不详细介绍了
# 4、事件对象event的属性
除了上面,只有鼠标和键盘中有的属性外,还有一些公共的所有事件对象都有的属性
- type:触发事件的类型
- target:事件源(操作的是哪个元素,哪个元素就是事件源)
- 在不兼容的浏览器中可以使用srcElement获取,也代表的是事件源
- preventDefault():用来阻止默认行为的方法
- 不兼容的浏览器中用ev.returnValue=false也可以阻止默认行为
- stopPropagation():阻止冒泡传播
- 不兼容的浏览器中用ev.cancelBubble=true也可以阻止默认行为
# 四、事件传播机制
# 思维导图
事件传播机制:当某个元素的相关事件行为触发时,浏览器会做三件事情:(可以通过dir(Event)产看Event类上的信息)
# 1、冒泡传播机制
Event.prototype:Event 原型上记录了冒泡传播的顺序
1、捕获阶段:=>CAPTURING_PHASE:1
从最外层向最里层事件源依次进行查找
- 目的:是为冒泡阶段事先计算好传播的层级路径
2、目标阶段:=>AT_TARGET:2
- 当前元素的相关事件行为触发
3、冒泡传播:=>BUBBLING_PHASE:3
- 触发当前元素的某一个事件行为,不仅它的这个行为被触发了, 而且它所有的祖先元素(一直到window)相关的事件行为都会被依次触发(从内到外的顺序)
# 2、阻止冒泡传播
ev.stopPropagation()
- 不兼容的浏览器中用ev.cancelBubble=true也可以阻止默认行为
# 小案例:实现放大镜效果(简易)
效果图:
# 1、HTMl
<div id="fd">
<div class="lit_box">
<div class="mask"></div>
<img src="images/banner1.jpg" alt="">
</div>
<div class="big_box">
<img src="images/banner1.jpg" alt="">
</div>
</div>
# 2、CSS
#fd {
margin: 20px auto;
}
.lit_box {
width: 200px;
height: 200px;
border: 1px solid #333;
position: relative;
top: 0;
left: 0;
}
.lit_box img {
width: 100%;
height: 100%;
}
.lit_box .mask {
width: 100px;
height: 100px;
background: rgba(238, 132, 10, 0.8);
position: absolute;
top: 0;
left: 0;
display: none;
cursor: move;
}
.big_box {
width: 400px;
height: 400px;
border: 1px solid #333;
position: relative;
left: 210px;
top: -201px;
overflow: hidden;
display: none;
}
.big_box img {
position: absolute;
width: 800px;
height: 800px;
}
# 3、JS
let lit = document.querySelector('.lit_box'),
mask = document.querySelector('.mask'),
big = document.querySelector('.big_box'),
bigImg = big.querySelector('img');
lit.onmouseenter = function () {
mask.style.display = 'block';
big.style.display = 'block';
}
lit.onmouseleave = function () {
mask.style.display = 'none';
big.style.display = 'none';
}
lit.onmousemove = function (e) {
let o = offset(this);
let l = e.pageX - o.left - mask.clientWidth / 2,
t = e.pageY - o.top - mask.clientHeight / 2;
let maxL = this.clientWidth - mask.clientWidth,
maxT = this.clientHeight - mask.clientHeight;
l = l < 0 ? 0 : (l > maxL ? maxL : l);
t = t < 0 ? 0 : (t > maxT ? maxT : t);
mask.style.left = l + 'px';
mask.style.top = t + 'px';
let n = big.clientHeight / mask.clientHeight;
bigImg.style.left = -l * n + 'px';
bigImg.style.top = -t * n + 'px';
}
function offset(ele) {
let l = ele.offsetLeft,
t = ele.offsetTop;
let parent = ele.offsetParent;
while (parent) {
l += parent.clientLeft + parent.offsetLeft;
t += parent.clientTop + parent.offsetTop;
parent = parent.offsetParent;
}
return {
top: t,
left: l
}
}
# 五、事件委托
又叫做事件代理
- 核心:
- 基于事件的冒泡传播机制完成
- 原理:
- 利用事件的冒泡传播机制,只给最外层容器的相关事件行为绑定方法,这样不管触发容器内部哪一个后代元素的相关事件行为,都会传播到容器上,触发它的对应事件行为,
- 在执行的方法中,可以基于ev.target来判断事件源,从而做不同的事情;避免给后台元素一个个的注册事件绑定,性能有很大的提高;
# 1、应用场景一
如果一个容器中很多元素都要在触发某一事件的时候做一些事情的时候,
- 原始方案:给元素每一个都单独进行事件绑定
- 基于事件委托:
- 我们只需要给当前容器的这个事件行为绑定方法,
- 这样不论是触发后代中哪一个元素的相关事件行为,由于冒泡传播机制,当前容器绑定的方法也都要被触发执行
- 想知道点击的是谁(根据是谁做不同的事情),只需要基于事件对象中的ev.target事件源获取即可
# 2、应用场景二
数据是动态绑定渲染的,要给每一条数据绑定事件行为时,选用事件委托,就可以实现动态点击的处理了,不用在逐一获取绑定了
# 3、应用场景三
除某某事件源以外的其它事件源,操作的时候统一做某事的,基本上都要基于事件委托解决
# 4、优点
- =>基于事件委托实现,整体性能要比一个个的绑定方法高出50%左右
- =>如果多元素触发,业务逻辑属于一体的,基于事件委托来处理更加好
- =>某些业务场景只能基于事件委托处理