原文链接:http://javascript.info/keyboard-events,translate with ❤️ by zhangbao.
在深入键盘事件之前,需要注意的是,还有其他的“输入”方式。例如:在手机设备上可以使用语音识别,或者用鼠标复制/粘贴进行输入。
因此,如果我们想要追踪 <input>
输入框,仅使用键盘事件是不够的。有一个叫 input
事件可以观察到任何情境下的 <input>
输入框里值的改变,有时它可能才是更好的选择。我们会在后面的《事件:change、input、cut、copy 和 paste》一章中介绍。
键盘事件用于处理键盘操作(虚拟键盘也包括在内)。例如,响应箭头按键 Up
、Down
或者热键(包括组合键)。
试验台
为了更好地理解键盘事件,可以点点下面的试验台。
在文本域中尝试不同的组合键查看效果。
keydown 和 keyup
keydown
事件在按下鼠标时触发;keyup
事件在释放鼠标时触发。
event.code 和 event.key
事件对象的 key
属性用来获得按键表示的字符, code
属性用来获得“物理按键码”。
例如,按下按键 Z
的同时,可以选择按下/不按下 Shift
键。如此一来,我们将得到两个不同的字符:大写的 Z
和小写的 z
。
event.key
属性对应实实在在打印出来的那个字符,可能有所不同;但打印出的 event.code
值始终是一样的:
按键 | event.key |
event.code |
---|---|---|
Z |
z (小写的) |
KeyZ |
Shift+Z |
Z (大写的) |
KeyZ |
对于不同语言的键盘,得到的 event.key
值可能就不是“Z
”了,而 event.code
值始终是“KeyZ
”。
“**KeyZ``”**和其他按键码
键盘上不同的按键有不同的按键码。按键码可参考《UI 事件码规范》:
例如:
字符键的键码值,是“
Key<letter>
”的形式:例如“KeyA
”、“KeyB
”等。数字键的键码值,是“
Digital<number>
”的形式:例如“Digital0
”、“Digital1
”等。其他按键的键码值按照名字的不同而不同:例如“
Enter
”、“Backspace
” 和“Tab
”等。有几种常规键盘布局,规范为每种布局都指定了对应的按键代码。
请参阅《规范的字母数字部分》以获得更多按键码信息。
注意**大小写:是“**KeyZ
**”,不是“****keyZ**
”虽然很明显,但还是经常会犯错的地方。
注意,是“
KeyZ
”,不是“keyZ
”!像event.code === "keyZ"
这种检查是无效的。 “Key
”必须是首字母大写的形式。
如果一个按键不代表任何字符呢?例如,Shift
、F1
或其他一些,对于这类按键,event.key
与 event.code
值基本一致。
按键 | event.key |
event.code |
---|---|---|
F1 |
F1 |
F1 |
Backspace |
Backspace |
Backspace |
Shift |
Shift |
ShiftRight 或 ShiftLeft |
请注意,event.code
的值能让我们确定按下的是哪个按键。例如,大多数的键盘有两个 Shift
按键:左边一个、右边一个。event.code
就能告诉我们按下的是哪一个,event.key
则强调被按下按键的含义“Shift
”。
如果需要处理热键:比如 Ctrl+Z
(对应 Mac 上的 Cmd+Z
)。许多文本编辑器绑定的默认操作是“撤销”。我们可以用 keydown
事件监听哪个按键被按下了——监测什么时候我们使用热键了。
请回答一个问题,在监听器中,我们应该检查 event.key
的值还是 event.code
的值呢?
请停下来想想。
……
想到了吗?
如果你已经理解了前面所说的,答案很明显,应该使用 event.code
而不是 event.key
。因为 event.key
的值依赖语言环境或 CapsLock
按键是否开启,而 event.code
的值总是一致的,是于键盘上的按键唯一绑定的。
document.addEventListener('keydown', function (event) {
if (event.code === 'keyZ' && (event.ctrlKey || event.metaKey)) {
alert('撤销');
}
});
自动重复
如果一个按键长时间按下不释放,就会重复触发 keydown
事件;当释放按键时,触发一次 keyup
事件。所以结果会看到触发许多的 keydown
事件和一个 keyup
事件。
所有重复 keydown
事件的事件对象的 event.repeat
属性值都为 true
。
默认行为
默认行为会有所不同,因为键盘操作可能会启动许多可能的行为。
例如:
出现一个字符(最明显的输出了)。
删除一个字符(按下
Delete
按键)。滚动页面(按下
PageDown
按键)。浏览器打开“保存”页面对话框(
Ctrl+S
)。……
阻止 keydown
事件的默认行为可以取消大多数(除个别操作系统级的快捷键外)上述行为的发生 。例如,Windows 系统里的 Alt+F4
会关闭当前浏览器窗口,这是使用 JavaScript 无法阻止的行为。
下例中,<input>
限制只能输入手机号码,不接受数字、+
、()
和 -
外的其他符号:
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') || key === '+' || key === '(' || key === ')' || key === '-';
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">
需要注意的是,在输入框中按下像 Backspace
、Left
、Right
和 Ctrl+V
等这样特殊的按键也变失效了,这是过滤算法 checkPhoneKey
带来的副作用。
我们放松一下规则:
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-' ||
key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace';
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">
现在箭头按键和删除按键都可以使用了。
但是我们仍然可以通过鼠标右键粘贴输入文本,所以这也不是百分之百可信赖的。我们可以先让它这样,因为多数场景下都是可行的。另一个可选方案是检查 input
事件,这个事件会在每次输入框内容修改后触,我们可以在处理器中检查新的值,无效的话就修改它。
遗留问题
过去还会使用 keypress
事件,还有事件对象上的 keyCode
、charCode
和 which
属性。
因为这些事件/事件属性并不是浏览器全兼容的,标准开发人员决定弃用它们。旧代码仍然有效,是因为浏览器还在支持它们,但是完全不需要再使用它们了。
曾几何时,本章中包含了对它们的详细描述,但现在我们可以忘记它们了。
总结
按下按键(可能是一个符号按键或是像 Shift
、Ctrl
这样的特殊按键)会触发键盘事件。唯一的例外是有时出现在笔记本键盘上的 Fn
键,它没有键盘事件,因为它通常在比操作系统更低的级别上实现的。
键盘事件:
keydown
——按下鼠标时触发(按住不放则会重复触发)keyup
——释放鼠标时触发。
主要的键盘事件对象属性:
code
—— “按键码”(比如“KeyA
”、“ArrowLeft
”等),表示按键在键盘上的物理位置。key
——字符(比如“A
”、“a
”等),对于非字符按键,值通常跟code
一致。
过去,键盘事件有时用于跟踪表单字段中的用户输入。这并不可靠,因为输入有多种方式。我们可以使用 input
/change
事件处理用户输入(在《事件:change、input、cut、copy 和 paste》一章中讲到)。它们会在输入值改变后触发,包括鼠标或语音识别的输入。
当我们明确使用键盘操作的时候,才去使用键盘事件。例如,响应热键和特殊按键的操作。
练习题
问题
一、扩展热键
创建一个函数 runOnKeys(func, code1, code2, ... code_n)
仅在同时按下按键码 code1
, code2
, … code_n
时,才去执行回调函数 func
。
例如下面代码里,仅在同时按下“Q
”和“W
”的时候(所有语言环境下,不管是否开启 CapsLock 键),调用 alert
:
runOnKeys(
() => alert("Hello!"),
"KeyQ",
"KeyW"
);
答案
一、扩展热键
我们会用到两个事件处理器:document.onkeydown
和 document.onkeyup
。
集合 pressed
中保存现在按下的按键。
第一个处理器用来添加按键,第二个处理器用来删除按键。每次 keydown
的时候,我们就检查下是否按下了所有想要按下的按键,如果是的话,就执行回调函数。
function runOnKeys(func, ...codes) {
let pressed = new Set();
document.addEventListener('keydown', function(event) {
pressed.add(event.code);
for (let code of codes) { // 是否按下了所有想要按下的按键
if (!pressed.has(code)) {
return;
}
}
// 好的,都按下了
// 在 alert 的时候,如果用户释放了按键
// JavaSript 不会接收到"keyup"事件
// pressed 集合里仍然保留所有按下的按键信息
// 为了避免"粘性"按键,我们重置了状态(清空了按键集合)
// 如果用户想要再一次执行热键回调,就需要再一次全部按下按键
pressed.clear();
func();
});
document.addEventListener('keyup', function(event) {
pressed.delete(event.code);
});
}
(完)