2022 年 6 月 19 日

鼠标事件

在本章中,我们将更详细地了解鼠标事件及其属性。

请注意:此类事件可能不仅来自“鼠标设备”,还来自其他设备,例如手机和平板电脑,在这些设备中,此类事件被模拟以实现兼容性。

鼠标事件类型

我们已经看到了一些这些事件

mousedown/mouseup
鼠标按钮在某个元素上单击/释放。
mouseover/mouseout
鼠标指针进入/离开某个元素。
mousemove
每次鼠标在某个元素上移动都会触发该事件。
click
如果使用的是鼠标左键,则在同一个元素上触发 mousedown 然后触发 mouseup 之后触发。
dblclick
在短时间内在同一个元素上单击两次后触发。现在很少使用。
contextmenu
在按下鼠标右键时触发。还有其他打开上下文菜单的方法,例如使用特殊键盘键,在这种情况下它也会触发,因此它不完全是鼠标事件。

…还有其他几个事件,我们稍后会介绍。

事件顺序

正如您从上面的列表中看到的,用户操作可能会触发多个事件。

例如,左键单击首先触发 mousedown(在按下按钮时),然后在释放按钮时触发 mouseupclick

当单个操作启动多个事件时,它们的顺序是固定的。也就是说,按 mousedownmouseupclick 的顺序调用处理程序。

单击下面的按钮,您将看到这些事件。也可以尝试双击。

在下面的测试台上,所有鼠标事件都会被记录,如果它们之间有超过 1 秒的延迟,则它们将由水平规则分隔。

此外,我们可以看到 button 属性,它允许我们检测鼠标按钮;如下所述。

鼠标按钮

与单击相关的事件始终具有 button 属性,该属性允许获取确切的鼠标按钮。

我们通常不将其用于 clickcontextmenu 事件,因为前者仅在左键单击时发生,而后者仅在右键单击时发生。

另一方面,mousedownmouseup 处理程序可能需要 event.button,因为这些事件会在任何按钮上触发,因此 button 允许区分“右键按下”和“左键按下”。

event.button 的可能值为

按钮状态 event.button
左键(主按钮) 0
中键(辅助按钮) 1
右键(次按钮) 2
X1 按钮(后退) 3
X2 按钮(前进) 4

大多数鼠标设备只有左右按键,因此可能的值是02。触控设备在用户点击时也会生成类似的事件。

此外,还有event.buttons属性,它以整数形式包含当前按下的所有按键,每个按键一位。实际上,此属性很少使用,如果需要,可以在MDN中找到详细信息。

过时的event.which

旧代码可能使用event.which属性,这是获取按钮的旧式非标准方法,可能的值有

  • event.which == 1 – 左键,
  • event.which == 2 – 中键,
  • event.which == 3 – 右键。

目前,event.which已弃用,我们不应使用它。

修饰键:shift、alt、ctrl 和 meta

所有鼠标事件都包含有关按下的修饰键的信息。

事件属性

  • shiftKey: Shift
  • altKey: Alt(或 Mac 上的Opt
  • ctrlKey: Ctrl
  • metaKey: Mac 上的Cmd

如果在事件期间按下了相应的键,则它们为true

例如,下面的按钮仅在Alt+Shift+单击时才有效

<button id="button">Alt+Shift+Click on me!</button>

<script>
  button.onclick = function(event) {
    if (event.altKey && event.shiftKey) {
      alert('Hooray!');
    }
  };
</script>
注意:在 Mac 上,通常使用Cmd代替Ctrl

在 Windows 和 Linux 上,有修饰键AltShiftCtrl。在 Mac 上,还有一个:Cmd,对应于属性metaKey

在大多数应用程序中,当 Windows/Linux 使用Ctrl时,Mac 上使用Cmd

也就是说:Windows 用户按Ctrl+EnterCtrl+A时,Mac 用户将按Cmd+EnterCmd+A,依此类推。

因此,如果我们想支持Ctrl+单击之类的组合,那么对于 Mac,使用Cmd+单击是有意义的。这对于 Mac 用户来说更方便。

即使我们想强制 Mac 用户Ctrl+单击——这有点困难。问题是:在 macOS 上,使用Ctrl进行左键单击会被解释为右键单击,它会生成contextmenu事件,而不是像 Windows/Linux 那样的click

因此,如果我们希望所有操作系统的用户都能感到舒适,那么除了ctrlKey之外,我们还应该检查metaKey

对于 JS 代码,这意味着我们应该检查if (event.ctrlKey || event.metaKey)

还有移动设备

键盘组合作为工作流的补充是好的。因此,如果访问者使用键盘,它们将起作用。

但是,如果他们的设备没有键盘,那么应该有一种方法可以在没有修饰键的情况下使用。

坐标:clientX/Y、pageX/Y

所有鼠标事件都以两种方式提供坐标

  1. 相对于窗口:clientXclientY
  2. 相对于文档:pageXpageY

我们已经在 坐标 章节中介绍了它们之间的区别。

简而言之,相对于文档的坐标 pageX/Y 从文档的左上角开始计算,并且在页面滚动时不会改变,而 clientX/Y 从当前窗口的左上角开始计算。当页面滚动时,它们会改变。

例如,如果我们有一个大小为 500x500 的窗口,并且鼠标位于左上角,那么无论页面如何滚动,clientXclientY 都是 0

如果鼠标位于中心,那么无论鼠标在文档中的什么位置,clientXclientY 都是 250。它们在这一点上类似于 position:fixed

将鼠标移到输入字段上以查看 clientX/clientY(示例在 iframe 中,因此坐标相对于该 iframe

<input onmousemove="this.value=event.clientX+':'+event.clientY" value="Mouse over me">

防止在 mousedown 时进行选择

双击鼠标有一个副作用,在某些界面中可能会令人不安:它会选择文本。

例如,双击以下文本会将其选中,除了我们的处理程序之外

<span ondblclick="alert('dblclick')">Double-click me</span>

如果按下鼠标左键,并且在不释放的情况下移动鼠标,也会进行选择,这通常是不需要的。

有多种方法可以防止选择,您可以在 选择和范围 章节中阅读。

在这种特殊情况下,最合理的方法是在 mousedown 上防止浏览器操作。它可以防止这两种选择

Before...
<b ondblclick="alert('Click!')" onmousedown="return false">
  Double-click me
</b>
...After

现在,加粗元素不会在双击时被选中,并且按其上的左键不会开始选择。

请注意:其中的文本仍然可以选择。但是,选择不应该从文本本身开始,而应该在文本之前或之后开始。通常这对用户来说很好。

防止复制

如果我们想禁用选择以保护我们的页面内容不被复制粘贴,那么我们可以使用另一个事件:oncopy

<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>

如果您尝试复制 <div> 中的一段文本,那将不起作用,因为默认操作 oncopy 已被阻止。

当然,用户可以访问页面的 HTML 源代码,并从中获取内容,但不是每个人都知道如何操作。

摘要

鼠标事件具有以下属性

  • 按钮:button

  • 修饰键(如果按下则为 true):altKeyctrlKeyshiftKeymetaKey(Mac)。

    • 如果你想处理 Ctrl,那么不要忘记 Mac 用户,他们通常使用 Cmd,因此最好检查 if (e.metaKey || e.ctrlKey)
  • 窗口相对坐标:clientX/clientY

  • 文档相对坐标:pageX/pageY

mousedown 的默认浏览器操作是文本选择,如果它对界面不好,则应该阻止它。

在下一章中,我们将看到有关指针移动后事件的更多详细信息,以及如何跟踪其下的元素更改。

任务

重要性:5

创建一个列表,其中元素可选择,就像在文件管理器中一样。

  • 单击列表元素仅选择该元素(添加类 .selected),取消选择所有其他元素。
  • 如果使用 Ctrl(对于 Mac,为 Cmd)单击,则在元素上切换选择,但不会修改其他元素。

演示

P.S. 对于此任务,我们可以假设列表项仅为文本。没有嵌套标记。

P.P.S. 防止单击时浏览器对文本进行原生选择。

为任务打开沙箱。

教程地图

评论

在评论之前阅读此内容…
  • 如果你有改进建议 - 请 提交 GitHub 问题或提交拉取请求,而不是评论。
  • 如果你无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标记,对于多行 - 将它们包装在 <pre> 标记中,对于超过 10 行 - 使用沙箱(plnkrjsbincodepen…)