# 一般事件处理
# 常用方式⭐
onclick()
—— HTML 中写法elem.onclick = f
—— DOM 属性调用elem.addEventListener(event, f[, options])
—— 事件监听,options
对象有如下属性:once
:如果为true
,那么会在被触发后自动删除监听器。capture
:事件处理的阶段,参考 冒泡和捕获 一章中的介绍。由于历史原因,options
也可以是false/true
,它与{capture: false/true}
相同。passive
:如果为true
,那么处理程序将不会调用preventDefault()
elem.removeEventListener(event,f[,options])
—— 移除事件,注意这里的f
必须和addEventListener
指向同一个函数
上述所有的
f
都可以有一个event
参数,可以通过type
获取事件类型(如:click
)、currentTarget
获取处理事件的元素,相当于当时的this
对象
# handleEvent
addEventListener(event,f)
,第二个参数不仅可以传入一个函数,也可以传入一个对象 obj,事件发生时,会自动调用对象内的 handleEvent(event)
函数,我们可以这样使用:
<button id="elem">Click me</button>
<script>
let obj = {
handleEvent(event) {
alert(event.type + " at " + event.currentTarget);
}
};
elem.addEventListener('click', obj);
</script>
或者可以使用一个类:
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
switch(event.type) {
case 'mousedown':
elem.innerHTML = "Mouse button pressed";
break;
case 'mouseup':
elem.innerHTML += "...and released.";
break;
}
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
本质上是一样,看实际业务场合使用。
# 冒泡事件
# 概念
简单来说:
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
点击 p
的时候,会依次弹出 [p、div、form]
,因为当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。
# 阻止冒泡⭐
event.stopPropagation()
可以阻止冒泡,但是只能阻止当前元素不要继续冒泡,但是只有当前的这个event
会停止冒泡,其他的event2/3/4
还会继续冒泡event.stopImmediatePropagation()
可以用于停止冒泡,并阻止当前元素上的处理程序运行。使用该方法之后,其他处理程序就不会被执行。
# event.target
在冒泡过程中, event.target
指向的是当前冒泡到那个元素,而 this
是 event
发生的元素,注意区分。
# 事件委托⭐
事件委托 是一种处理这种情况:
在一个容器中,会有多个相同或类似的目标元素对同样的事件做一些处理,比如有一个 9x9 的表格,每一个 td 鼠标放上去的时候修改颜色。
处理方式一般是通过 container.addEventListener(event,(e)=>{xxxx})
,根据 e.target
获取当前活跃元素来进行操作。
这是一种处理模式,具体的一些例子见课程 。
# 阻止浏览器默认事件
- 在
on<event>
中使用return false
- 在
addEventListener
中使用event.preventDefault()
- 在
addEventListener
这个方法的第三个参数options
对象中设置passive:true
表示程序 不会 调用event.preventDefault()
,注意,是不会! - 判断默认事件是否被阻止,使用
event.defaultPrevented
,返回一个bool
值
# 鼠标事件
# 常用事件
mousedown/mouseup
在元素上点击/释放鼠标按钮。mouseover/mouseout
鼠标指针从一个元素上移入/移出。mousemove
鼠标在元素上的每个移动都会触发此事件。click
如果使用的是鼠标左键,则在同一个元素上的 mousedown 及 mouseup 相继触发后,触发该事件。dblclick
在短时间内双击同一元素后触发。如今已经很少使用了。contextmenu
在鼠标右键被按下时触发。还有其他打开上下文菜单的方式,例如使用特殊的键盘按键,在这种情况下它也会被触发,因此它并不完全是鼠标事件。oncopy
复制事件
当我们单击时,触发的顺序是:
mousedown
=>mouseup
=>click
# 按钮判断
鼠标按键状态 | event.button |
---|---|
左键 (主要按键) | 0 |
中键 (辅助按键) | 1 |
右键 (次要按键) | 2 |
X1 键 (后退按键) | 3 |
X2 键 (前进按键) | 4 |
# 鼠标移动事件
这里我们主要介绍两套移入移除方法:
mouseover/mouseout
:鼠标移入/移出
他们都有 target
和 relatedTarget
两个属性:
- 对于
mouseover
,target
是移入对象,relatedTarget
是来自的对象 - 对于
mouseout
,target
是离开对象,relatedTarget
是离开后移入的对象
不管是移入移除,
relatedTarget
可以为null
- 父元素移入子元素的时候,会触发父元素的
mouseout
,然后触发子元素的mouseover
,注意子元素的mouseover
是会 冒泡 的,也就是这时候会再触发父元素的mouseover
,我们可以借用这个特性做 事件委托
mouseenter/mouseleave
这也是一套移入移除方法,但是他和mouseover/mouseout
最大的区别在于:- 进入父元素后,触发父元素的
mouseenter
,不管你在这个父元素的里面如何进出任何子元素上,都不会触发父元素的mouseleave
,只会触发子元素的mouseenter/mouseleave
,查看示例代码 如下 - 事件 mouseenter/mouseleave 不会冒泡。
- 进入父元素后,触发父元素的
# 拖放事件⭐
拖放事件我们有一套基础的拖放算法如下所示:
- 在 mousedown 上 —— 根据需要准备要移动的元素(也许创建一个它的副本,向其中添加一个类或其他任何东西)。
- 然后在 mousemove 上,通过更改 position:absolute 情况下的 left/top 来移动它。
- 在 mouseup 上 —— 执行与完成的拖放相关的所有行为。
这里用一个拖放小球的例子作为学习,注意看 js 中的注释
再来看一个滑动条(slider)的封装
这里说明一下一下为什么 mouseup
和 mousemove
都要加在 document
上,因为
mousemove
是一定要在document
上的,如果放在thumb
上,你鼠标移动快到离开了thumb
那么mousemove
事件就立刻失效了mouseup
如果你拖动的时候鼠标离开了thumb
然后松开,那么mouseup
就失效
# 总结一下【拖放】⭐
- 遵循
mousedown
里绑定mousemove 和 mouseup
mousemove
一般都放在document
,因为鼠标拖动时候快了就会离开元素mouseup
想清楚鼠标离开元素是否需要触发mouseup
再考虑绑定在哪- 一半都会要计算
shiftX/Y
,一般都是event.clientX - ball.getBoundingClientRect().left
这种形式 document.elementFromPoint
可以获取鼠标指针下当前嵌套最深的元素,但是记得hidden
你鼠标拖动的元素,获取了之后再解除hidden
,不然可能返回的永远都是它
# 指针事件
指针事件兼容 IE 10
我们可以在现代浏览器使用 pointer<event>
来代替 mouse<event>
:
指针事件 | 类似的鼠标事件 |
---|---|
pointerdown | mousedown |
pointerup | mouseup |
pointermove | mousemove |
pointerover | mouseover |
pointerout | mouseout |
pointerenter | mouseenter |
pointerleave | mouseleave |
pointercancel | - |
gotpointercapture | - |
lostpointercapture | - |
- 在触屏设备中,指针事件会更加好用
- 在拥有所有鼠标事件属性的情况下,还有如下事件:
pointerId
—— 触发当前事件的指针唯一标识符。 浏览器生成的。使我们能够处理多指针的情况,例如带有触控笔和多点触控功能的触摸屏(下文会有相关示例)。pointerType
—— 指针的设备类型。必须为字符串,可以是:“mouse”、“pen” 或 “touch”。 我们可以使用这个属性来针对不同类型的指针输入做出不同响应。isPrimary
—— 当指针为首要指针(多点触控时按下的第一根手指)时为true
。
关于多点触控介绍详见教程
# 键盘事件
# keydown & keyup
顾名思义,不解释
# event.code & event.key
Key | event.key | event.code |
---|---|---|
Z | z (小写) | KeyZ |
Shift+Z | Z (大写) | KeyZ |
F1 | F1 | F1 |
Backspace | Backspace | Backspace |
Shift | Shift | ShiftRight 或 ShiftLeft |
观察上面我们会发现,同一个按键 Z,可以与或不与 Shift 一起按下。我们会得到两个不同的字符:小写的 z 和大写的 Z。 event.key
正是这个字符,并且它将是不同的。但是, event.code
是相同的。
event.code
准确地标明了哪个键被按下了。例如,大多数键盘有两个 Shift 键,一个在左边,一个在右边。 event.code
会准确地告诉我们按下了哪个键,而 event.key
对按键的“含义”负责:它是什么(一个 “Shift”)。
# 组合键
所有的鼠标事件都包含有关按下的组合键的信息。 事件属性:
shiftKey
:ShiftaltKey
:Alt(或对于 Mac 是 Opt)ctrlKey
:CtrlmetaKey
:对于 Mac 是 Cmd
使用方式:
document.addEventListener('keydown', function(event) {
if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) {
alert('Undo!')
}
});
# 滚动事件⭐
scroll
事件允许对页面或元素滚动作出反应。我们可以在这里做一些有用的事情。
这里举几个 现代js教程 的例子:
- 上拉加载更多,注意
getBoundingClientRect().bottom
是浏览器视窗顶部到文档底部的距离,且它永不为0,因为当他等于clientHeight
的时候就滚不动了。
- 回到顶部
- 图片懒加载,基本原理:src 占位图片,data-src是实际地址,在判断图片头或尾显示出的时候,把src替换为data-src
# 选择与范围
有一些特殊的场合下,我们也许会需要操作选择范围(鼠标拖黑网页内容),或者读取下、用户选择范围这样的操作,可以使用到 Selection 和 Range 对象,由于应用场合比较少,不展开细说,细节参考教程