原文链接:https://javascript.info/mouse-events-basics,translate with ❤️ by zhangbao.
鼠标事件不是仅来自于“鼠标操作”,在触屏设备上(为了兼容)也能触发鼠标事件。
在本章中,我们将详细介绍鼠标事件及其属性。
鼠标事件类型
我们可以把鼠标事件分成两类:“简单”和“复杂”。
简单事件
最常用的包括:
mousedown
/mouseup
鼠标按键在元素上点击/释放。
mouseover
/mouseout
鼠标光标进入/离开某个元素。
mousemove
鼠标在元素上移动。
……还有其他一些事件类型,之后介绍。
复杂事件
click``
点击鼠标左键时,在 mousedown`、**
mouseup`** 事件之后触发。
contextmenu``
点击鼠标右键时,在 mousedown
、mouseup
事件之后触发。
dblclick
在一个元素上鼠标双击之后触发。
复杂事件由简单事件组成,因此理论上我们可以没有它们。但是复杂事件的存在,也为我们的编程带来便利。
事件顺序
一个操作可能会触发多个事件。
例如,当单击按下按钮时,首先触发 mousedown
,然后在释放时触发 mouseup
和 click
事件。
单个操作触发的多个事件,触发的事件顺序始终固定的。比如,点击事件是按照 mousedown
→ mouseup
→ click
这样的顺序触发。事件处理程序也是按照这个顺序调用:onclick
的处理程序在 onmouseup
的处理程序之后执行。
点击鼠标左键执行的事件顺序:
点击鼠标右键执行的事件顺序:
which
属性记录着我们按下的是哪个鼠标按键。
点击的鼠标按键:which
点击相关的事件对象中总是包含一个 which
属性,用来获得点击的是哪个鼠标按键。
它不用于 click
和 contextmenu
事件,因为前者仅在左键单击时发生,后者仅在右键单击时发生。
但是如果我们跟踪 mousedown
和 mouseup
,那么我们就需要它,因为这些事件是在任何按钮上都会触发的,由此就可以区分“右鼠标按下”和“坐鼠标按下”。
event.which
属性有三个可能的取值:
1:左键。
2:中键。
3:右键。
中间按钮现在有些奇怪,很少使用。
修饰键:Shift, Alt, Ctrl 和 Meta
所有的鼠标事件,都包含按下的修饰键信息。
这些修饰键属性分别是:
shiftKey
altKey
ctrlKey
metaKey
(Mac 系统的Cmd
键)
例如,下面的按钮只对“Alt+Shift
+点击”有效:
<button id="button">来 Alt+Shift+点击 我!</button>
<script>
button.onclick = function (event) {
if (event.altKey && event.shiftKey) {
alert('万岁!');
}
};
</script>
注意:**在 Mac 系统中,通常使用 **Cmd
** 而非 ****Ctrl**
在 Windows 和 Linux 系统中,可用的修饰键是
Alt
、Shift
和Ctrl
。在 Mac 系统中,还多了一个是Cmd
键,对应属性metaKey
。多数场景下,在 Windows 系统中使用的
Ctrl
键,在 Mac 系统中就变成使用Cmd
键。例如,Windows 系统用户按下Ctrl+Enter
或者Ctrl+A
,对应到 Mac 上就是Cmd+Enter
和Cmd+A
(Mac 中的大多数软件也都是选择使用Cmd
键而非Ctrl
键)。所以,如果我们想要支持组合键
Ctrl+点击
,那么在 Mac 中就要注意——是使用Cmd+点击
,对于 Mac 用户来说,这更符合使用习惯。即使强制 Mac 用户使用快捷键
Ctrl+点击
,也是挺困难的,问题是:Ctrl+左键点击
会被 Mac 系统理解为右击,进而触发contextmenu
事件,而不是像在 Windows/Linux 中那样被当成点击事件。因此,如果我们想让所有操作系统的用户都感觉满意,那么就得同时支持
ctrlKey
和metaKey
了。用 JavaScript 代码我们就应该这样检查:
if (event.ctrlKey || event.metaKey)
。注意移动设备
键盘组合键是一种工作方式,只要用户有键盘,就能操作。但是如果没有键盘设备,就需要使用另外一种替代方式。
坐标:clientX/Y,pageX/Y
所有鼠标事件都有两种坐标表示:
窗口坐标:
clientX
和clientY
。文档坐标:
pageX
和pageY
。
例如,我们有一个 500x500 大小的窗口。当鼠标在窗口左上角位置时,clientX
和 clientY
值都为 0
,即此时鼠标所在位置的窗口坐标是 (0, 0)
;如果鼠标在窗口中间位置时,那么 clientX
和 clientY
值都为 250
。而且不管文档滚动到在什么位置,这两个位置的窗口坐标始终不变,类似 position:fixed
。
查阅下面的代码结果,
<input onmousemove="this.value=event.clientX+':'+event.clientY" value="Mouse over me">
当我们的鼠标在输入框上移动的时候,输入框会实时显示当前鼠标的窗口坐标值。
文档坐标的原点位于文档左上角,而非窗口左上角。pageX
和 pageY
属性值类似 position: absolute
,反应的是文档坐标值。
关于坐标的更多的信息,会在《坐标系》一章中讲解。
mousedown 的时候不要选中
鼠标点击有副作用,可能会令人不安,那就是双击后选择文本。
如果我们想自己处理点击事件,那么“额外”选择功能看起来不太好。
例如,双击下面的文本除了会调用事件处理程序之外,还会选择它:
<b ondblclick="alert('dblclick')">双击我</b>
有一种 CSS 方法可以停止选择:《CSS UI Draft》中的 user-select
属性。
大多数浏览器使用该属性时候,都需要添加前缀:
<style>
b {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style>
Before...
<b ondblclick="alert('test')">
Unselectable
</b>
...After
现在双击“Unselectable”文本的话,就不会有选中效果了。
但是有个问题,就是文本是真的选中不了了。即使用户是从“Before…”开始一直拉到“…After”,选中部分也不包括“Unselectable”部分。我们真的想让我们的文字无法选中吗?
大多数时候,我们没有。用户可能有正当理由选择文本,复制或其他需要。如果我们不允许他们这样做,那可能会很不方便。所以这个解决方案并不是那么好。
我们想要的是避免双击选择,就是这样。
文本选择是 mousedown
事件的默认浏览器行为,所以替代解决方案是处理 mousedown
并阻止它,如下所示:
Before...
<b ondblclick="alert('test')" onmousedown="return false">
Unselectable
</b>
...After
现在加粗文字不会在鼠标双击的时候被选中。
但是加粗文本还是可以被选中的,但是不能从自身开始被选中,而是得经过之前或者之后的文本去选中它。但这样已经足够好了。
现在,双击时不会选中粗体文本。
而粗体文本仍然是可选的。但是,选择不能从粗体开始,而是从这段文本的开始或结束位置开始,这种方式通常更好。
取消选中
我们也可以选择不阻止选中,而是在“事后”取消文本选中。
这样做的:
Before...
<b ondblclick="getSelection().removeAllRanges()">
Unselectable
</b>
...After
如果双击粗体元素,则会出现选择,然后立即删除。 虽然看起来不太好看。
阻止复制
如果我们想要阻止选中效果,避免我们的内容被盗取,可以提使用另一个事件:
oncopy
。
<div oncopy="alert('禁止复制'); return false">
亲,
我们是不允许复制的页面内容。
如果你知道 JS 或者 HTML 的话,可以从源代码里获得。
</div>
如果您尝试复制
中的一段文本,那将无效,因为 oncopy
的默认行为被阻止了。当然,这不能阻止用户打开 HTML 源代码,但不是每个人都知道如何做到这一点。
总结
鼠标事件具有下列属性:
按钮:
which
。修饰键(按下则为
true
):altKey
、ctrlKey
、shiftKey
和metaKey
(Mac 系统)。- 如果想要处理
Ctrl
,不要忘记 Mac 用户,他们使用的是Cmd
,所以最好的检查方式是if (e.metaKey || e.ctrlKey)
。
- 如果想要处理
窗口坐标:
clientX/clientY
。文档坐标:
pageX/pageY
。
将文本选择视为点击的不良副作用也很重要。
这里提供了几种方式来阻止这个副作用:
CSS 属性
user-select: none
(大多数浏览器中都需要使用浏览器前缀形式),完全禁止文本选中。“事后”取消文字选中:
getSelection().removeAllRanges()
。阻止
mousedown
事件的默认行为(通常来说是最好的方式)。
练习题
问题
一、可选中列表项
创建一个列表,列表项都是可选的,就像文件管理器一样。
列表中只能有一个列表项被选中(添加类名 .selected),其他列表项不能选中。
鼠标点击时,如果同时按下 Ctrl 键,实现的是在点击上的切换选中效果,但不影响其他元素修改。
结果如下图:
PS. 本任务中的列表项仅包含文本,不存在内嵌标签。在点击文本的时候,阻止默认的文本选中行为。
答案
一、可选中列表项
ul.onclick = function(event) {
if (event.target.tagName === 'LI') {
if (event.ctrlKey || event.metaKey) {
toggleSelect(event.target);
} else {
singleSelect(event.target);
}
}
}
// 阻止点击时选中文本的默认行为
ul.onmousedown = function() {
return false;
};
function toggleSelect(li) {
li.classList.toggle('selected');
}
function singleSelect(li) {
let selected = ul.querySelectorAll('.selected');
for(let elem of selected) {
elem.classList.remove('selected');
}
li.classList.add('selected');
}
(完)