此类事件不仅可能来自于鼠标、触控板,还可能来自于对此类操作进行了模拟以实现兼容性的其他设备,例如手机和平板电脑
鼠标按键
事件及触发顺序
mousedown
、mouseup
:在元素上点击/释放鼠标按键click
:在元素上单击鼠标左键- 先触发
mousedown
,当鼠标左键被释放时,会依次触发mouseup
和click
- 先触发
dblclick
:在元素上短时间内双击鼠标左键mousedown -> mouseup -> click
乘 2 后,触发dblclick
contextmenu
:在元素上鼠标右键被按下时触发- 还有其他打开上下文菜单的方式,例如使用特殊的键盘按键,因此它并不完全是鼠标事件
- 鼠标右键按下依次触发
mousedown
和contextmenu
,当鼠标右键被释放时触发mouseup
鼠标按键
event.button
:获得按下的按键0
:左键(主要按键)1
:中键(辅助按键)2
:右键(次要按键)3
:X1 键(后退按键)4
:X2 键(前进按键)
event.which
:获得按下的按键,过时的1
:左键2
:中键3
:右键
event.buttons
:以整数的形式存储着当前所有按下的鼠标按键,每个按键一个比特位- 很少用到,MDN 文档
在 mousedown
和 mouseup
中可能需要。因为 click
和 dblclick
是左键;contextmenu
是右键
键盘组合键
防止在鼠标按下时的选择
双击鼠标会有副作用,在某些界面中可能会出现干扰:它会选择文本
比如,双击下面的文本,除了我们的处理程序外,还会选择文本:
<span ondblclick="alert('dblclick')">Double-click me</span>
如果按下鼠标左键,并在不松开的情况下移动鼠标,这也常常会造成不必要的选择
有多种防止选择的方法:选择(Selection)和范围(Range)
在这种情况下,最合理的方式是防止浏览器对 mousedown
进行操作。这样能够阻止刚刚提到的两种选择:
Before...
<b ondblclick="alert('Click!')" onmousedown="return false">
Double-click me
</b>
...After
现在,在双击时,粗体元素不会被选中,并且在粗体元素上按下鼠标左键也不会开始选择
请注意:其中的文本仍然是可选择的。但是,选择不应该开始于该文本自身,而应该在该文本之前或之后开始。通常,这对用户来说挺好的
防止复制
复制事件:copy
,阻止其默认行为即可无法复制:
<div oncopy="alert('Copying forbidden!');return false">
Dear user,
The copying is forbidden for you.
If you know JS or HTML, then you can get everything from the page source though.
</div>
当然,用户可以访问页面的 HTML 源码,并且可以从那里获取内容,但并不是每个人都知道如何做到这一点
鼠标移动
事件
mousemove
:鼠标在元素上的每个移动都会触发此事件mouseover
:鼠标指针移入(进入)元素时event.target
:当前移入的元素event.relatedTarget
:鼠标来自的那个元素- 即:鼠标从
relatedTarget
移入到target
mouseout
:鼠标指针移出(离开)元素时event.target
:鼠标移出的元素event.relatedTarget
:鼠标移动到的元素- 即:鼠标从
target
移出,到了relatedTarget
mouseenter/mouseleave
:类似于mouseover/mouseout
- 有
event.relatedTarget
- 区别:
- 元素内部与后代之间的转换不会产生影响
- 不会冒泡
- 有
relatedTarget
可以为 null
:意味着鼠标不是来自另一个元素,而是来自窗口之外。或者它离开了窗口
mouseover/mouseout
与 mouseenter/mouseleave
的区别:
- 是否冒泡
mouseover/mouseout
冒泡mouseenter/mouseleave
不冒泡
- 当前元素与后代元素
mouseover/mouseout
当鼠标指针从当前元素移动到其后代时会触发当前元素的mouseout
mouseenter/mouseleave
元素内部与后代之间的转换不会产生影响
鼠标指针从 #parent
移到 #child
,在 #parent
上会触发 mouseout
:
解释:鼠标指针位于单个元素上 —— 嵌套最多的那个元素(z-index
最大的那个),如果它转到另一个元素(甚至是一个后代),那么它将离开前一个元素
注意另一个重要的细节:后代的 mouseover
事件会冒泡。因此,如果 #parent
具有 mouseover
处理程序,它将被触发:
<div id="parent" onmouseover="mouselog(event)" onmouseout="mouselog(event)">parent
<div id="child">child</div>
</div>
如果将鼠标从 #parent
移动到 #child
,那么在 #parent
上有两个事件:
mouseout [target: parent]
(离开 parent),然后mouseover [target: child]
(来到 child,冒泡)
而 mouseenter/mouseleave
不同:鼠标指针在当前元素和其后代上都认为是 enter
触发频率
当鼠标移动时,就会触发 mousemove
事件,但这并不意味着每个像素都会导致一个事件
而是浏览器会有一个时间间隔检查鼠标的位置,如果发现了变化,就会触发事件
即:靠时间间隔判断鼠标移动,而不是像素点。这对性能很有好处
这意味着,如果访问者非常快地移动鼠标,那么某些 DOM 元素的事件可能被跳过:
如果鼠标从上图所示的 #FROM
快速移动到 #TO
元素,则中间的 <div>
(或其中的一些)元素可能会被跳过。mouseout
事件可能会在 #FROM
上被触发,然后立即在 #TO
上触发 mouseover
应该记住,鼠标指针并不会“访问”所有元素,它可以“跳过”一些元素
注意:虽然可能“跳过”,但如果 mouseover
或 mouseenter
被触发了,则离开时必定有 mouseout
或 mouseleave
鼠标拖放
使用鼠标事件来实现拖放(Drag’n’Drop)效果:
- 在
mousedown
上 —— 根据需要准备要移动的元素(也许创建一个它的副本,向其中添加一个类或其他东西) - 然后在
mousemove
上,通过更改position: absolute
情况下的left/top
来移动它 - 在
mouseup
上 —— 执行与完成的拖放相关的所有行为
注意:
- 浏览器对拖放可能有一些默认行为,可能需要禁用
ondragstart
mousemove
应该绑定到document
,因为在快速移动鼠标时,鼠标指针可能会跳转至文档中间的某个位置(甚至跳转至窗口外)- 判断潜在的 “droppable” 的元素
- 在“droppable”上绑定
mousemove
是不可行的,因为“draggable”才是z-index
更大的,底层“droppable”的mousemove
事件并不会被触发 - 正确做法:“draggable”上绑定
mousemove
,拿“droppable”:draggable.hidden = true
const droppable = document.elementFromPoint(event.clientX, event.clientY)
draggable.hidden = false
- 在“droppable”上绑定
在现代 HTML 标准中有一个 关于拖放的部分,详见 拖拽事件
原生拖放事件会有很多先进的特性以供开发者使用,但也有其局限性。例如,无法阻止从特定区域的拖动,无法将拖动变成“水平”或“竖直”的。还有很多其他使用它们无法完成的拖放任务。并且,移动设备对此类事件的支持非常有限
所以具体选用什么方案还是要看实际的需求
坐标
所有的鼠标事件都提供了 两种形式的坐标:
- 相对于窗口的坐标:
event.clientX
和event.clientY
- 相对于文档的坐标:
event.pageX
和event.pageY