此类事件不仅可能来自于鼠标、触控板,还可能来自于对此类操作进行了模拟以实现兼容性的其他设备,例如手机和平板电脑

鼠标按键

事件及触发顺序

  • mousedownmouseup:在元素上点击/释放鼠标按键
  • click:在元素上单击鼠标左键
    • 先触发 mousedown,当鼠标左键被释放时,会依次触发 mouseup 和 click
  • dblclick:在元素上短时间内双击鼠标左键
    • mousedown -> mouseup -> click 乘 2 后,触发 dblclick
  • contextmenu:在元素上鼠标右键被按下时触发
    • 还有其他打开上下文菜单的方式,例如使用特殊的键盘按键,因此它并不完全是鼠标事件
    • 鼠标右键按下依次触发 mousedowncontextmenu,当鼠标右键被释放时触发 mouseup

鼠标按键

  • event.button:获得按下的按键
    • 0:左键(主要按键)
    • 1:中键(辅助按键)
    • 2:右键(次要按键)
    • 3:X1 键(后退按键)
    • 4:X2 键(前进按键)
  • event.which:获得按下的按键,过时的
    • 1:左键
    • 2:中键
    • 3:右键
  • event.buttons:以整数的形式存储着当前所有按下的鼠标按键,每个按键一个比特位

在 mousedown 和 mouseup 中可能需要。因为 clickdblclick 是左键;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
    • 区别:
      1. 元素内部与后代之间的转换不会产生影响
      2. 不会冒泡

relatedTarget 可以为 null:意味着鼠标不是来自另一个元素,而是来自窗口之外。或者它离开了窗口

mouseover/mouseoutmouseenter/mouseleave 的区别:

  1. 是否冒泡
    • mouseover/mouseout 冒泡
    • mouseenter/mouseleave 不冒泡
  2. 当前元素与后代元素
    • 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 上有两个事件:

  1. mouseout [target: parent](离开 parent),然后
  2. mouseover [target: child](来到 child,冒泡)

mouseenter/mouseleave 不同:鼠标指针在当前元素和其后代上都认为是 enter

触发频率

当鼠标移动时,就会触发 mousemove 事件,但这并不意味着每个像素都会导致一个事件

而是浏览器会有一个时间间隔检查鼠标的位置,如果发现了变化,就会触发事件

即:靠时间间隔判断鼠标移动,而不是像素点。这对性能很有好处

这意味着,如果访问者非常快地移动鼠标,那么某些 DOM 元素的事件可能被跳过:

如果鼠标从上图所示的 #FROM 快速移动到 #TO 元素,则中间的 <div>(或其中的一些)元素可能会被跳过。mouseout 事件可能会在 #FROM 上被触发,然后立即在 #TO 上触发 mouseover

应该记住,鼠标指针并不会“访问”所有元素,它可以“跳过”一些元素

注意:虽然可能“跳过”,但如果 mouseovermouseenter 被触发了,则离开时必定有 mouseoutmouseleave

鼠标拖放

使用鼠标事件来实现拖放(Drag’n’Drop)效果:

  1. 在 mousedown 上 —— 根据需要准备要移动的元素(也许创建一个它的副本,向其中添加一个类或其他东西)
  2. 然后在 mousemove 上,通过更改 position: absolute 情况下的 left/top 来移动它
  3. 在 mouseup 上 —— 执行与完成的拖放相关的所有行为

注意:

  • 浏览器对拖放可能有一些默认行为,可能需要禁用 ondragstart
  • mousemove 应该绑定到 document,因为在快速移动鼠标时,鼠标指针可能会跳转至文档中间的某个位置(甚至跳转至窗口外)
  • 判断潜在的 “droppable” 的元素
    • 在“droppable”上绑定 mousemove 是不可行的,因为“draggable”才是 z-index 更大的,底层“droppable”的 mousemove 事件并不会被触发
    • 正确做法:“draggable”上绑定 mousemove,拿“droppable”:
      1. draggable.hidden = true
      2. const droppable = document.elementFromPoint(event.clientX, event.clientY)
      3. draggable.hidden = false

在现代 HTML 标准中有一个 关于拖放的部分,详见 拖拽事件

原生拖放事件会有很多先进的特性以供开发者使用,但也有其局限性。例如,无法阻止从特定区域的拖动,无法将拖动变成“水平”或“竖直”的。还有很多其他使用它们无法完成的拖放任务。并且,移动设备对此类事件的支持非常有限

所以具体选用什么方案还是要看实际的需求

坐标

所有的鼠标事件都提供了 两种形式的坐标

  1. 相对于窗口的坐标:event.clientX 和 event.clientY
  2. 相对于文档的坐标:event.pageX 和 event.pageY