18日 七月 2020

鼠标事件

在本章中,我们将详细介绍鼠标事件及其属性。

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

鼠标事件类型

我们已经见过了其中一些事件:

mousedown/mouseup
在元素上点击/释放鼠标按钮。
mouseover/mouseout
鼠标指针从一个元素上移入/移出。
mousemove
鼠标在元素上的每个移动都会触发此事件。
click
如果使用的是鼠标左键,则在同一个元素上的 mousedownmouseup 相继触发后,触发该事件。
dblclick
在短时间内双击同一元素后触发。如今已经很少使用了。
contextmenu
在鼠标右键被按下时触发。还有其他打开上下文菜单的方式,例如使用特殊的键盘按键,在这种情况下它也会被触发,因此它并不完全是鼠标事件。

……还有其他几种事件,我们稍后会学习它们。

事件顺序

从上面的列表中我们可以看到,一个用户操作可能会触发多个事件。

例如,点击鼠标左键,在鼠标左键被按下时,会首先触发 mousedown,然后当鼠标左键被释放时,会触发 mouseupclick

在单个动作触发多个事件时,事件的顺序是固定的。也就是说,会遵循 mousedownmouseupclick 的顺序调用处理程序。

点击(译注:即单击)下面的按钮,你会看到事件。并尝试双击它。

在测试台下面记录了所有的鼠标事件,如果它们之间的延迟时间超过 1 秒,那么它们会被水平分割线分开。

我们还可以看出 button 属性允许检测鼠标按钮,演示示例如下。

鼠标按钮

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

We usually don’t use it for click and contextmenu events, because the former happens only on left-click, and the latter – only on right-click.

From the other hand, mousedown and mouseup handlers we may need event.button, because these events trigger on any button, so button allows to distinguish between “right-mousedown” and “left-mousedown”.

The possible values of event.button are:

Button state event.button
Left button (primary) 0
Middle button (auxillary) 1
Right button (secondary) 2
X1 button (back) 3
X2 button (forward) 4

Most mouse devices only have the left and right buttons, so possible values are 0 or 2. Touch devices also generate similar events when one taps on them.

Also there’s event.buttons property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at MDN if you ever need it.

The outdated event.which

Old code may use event.which property that’s an old non-standard way of getting a button, with possible values:

  • event.which == 1 – left button,
  • event.which == 2 – middle button,
  • event.which == 3 – right button.

As of now, event.which is deprecated, we shouldn’t use it.

组合键:shift,alt,ctrl,meta

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

事件属性:

  • shiftKeyShift
  • altKeyAlt(或对于 Mac 是 Opt
  • ctrlKeyCtrl
  • metaKey:对于 Mac 是 Cmd

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

比如,下面这个按钮仅在 Alt+Shift+click 时才有效:

<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+click,那么对于 Mac 应该使用 Cmd+click。对于 Mac 用户而言,这更舒适。

即使我们想强制 Mac 用户使用 Ctrl+click —— 这非常困难。问题是:在 MacOS 上左键单击和 Ctrl 一起使用会被解释为 右键单击,并且会生成 contextmenu 事件,而不是像 Windows/Linux 中的 click 事件。

因此,如果我们想让所有操作系统的用户都感到舒适,那么我们应该将 ctrlKeymetaKey 一起进行检查。

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

还有移动设备

键盘组合是工作流的一个补充。这样,如果访客使用键盘操作 —— 它们就会起作用。

但是,如果访客的设备没有键盘 —— 那么这里应该有另一种不使用键盘也能做到这一点的方式。

坐标:clientX/Y,pageX/Y

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

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

We already covered the difference between them in the chapter 坐标.

In short, document-relative coordinates pageX/Y are counted from the left-upper corner of the document, and do not change when the page is scrolled, while clientX/Y are counted from the current window left-upper corner. When the page is scrolled, they change.

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

如果鼠标位于中间,那么 clientXclientY 均为 250。这与它在文档中的位置无关。在这方面,它们类似于 position:fixed

将鼠标移动到输入字段上,可以看到 clientX/clientY(此示例位于 iframe 中,因此坐标是相对于 iframe 的):

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

防止在鼠标按下时的选择

双击鼠标会有副作用,在某些界面中可能会出现干扰:它会选择文本。

比如,双击下面的文本,除了我们的处理程序外,还会选择文本:

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

如果按下鼠标左键,并在不松开的情况下移动鼠标,这也常常会造成不必要的选择。

有多种防止选择的方法,你可以在 选择(Selection)和范围(Range) 一章中详细阅读。

在这种情况下,最合理的方式是防止浏览器对 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 issue 或 pull request,而不是在这评论。
  • 如果你对教程的内容有不理解的地方 — 请详细说明。
  • 使用 <code> 标签插入只有几个词的代码,插入多行代码可以使用 <pre> 标签,对于超过 10 行的代码,建议你使用沙箱(plnkrJSBincodepen…)