原文链接:http://javascript.info/keyboard-events,translate with ❤️ by zhangbao.

在深入键盘事件之前,需要注意的是,还有其他的“输入”方式。例如:在手机设备上可以使用语音识别,或者用鼠标复制/粘贴进行输入。

因此,如果我们想要追踪 <input> 输入框,仅使用键盘事件是不够的。有一个叫 input 事件可以观察到任何情境下的 <input> 输入框里值的改变,有时它可能才是更好的选择。我们会在后面的《事件:change、input、cut、copy 和 paste》一章中介绍。

键盘事件用于处理键盘操作(虚拟键盘也包括在内)。例如,响应箭头按键 UpDown 或者热键(包括组合键)。

试验台

为了更好地理解键盘事件,可以点点下面的试验台。

在文本域中尝试不同的组合键查看效果。

键盘事件:keydown 和 keyup - 图1

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

键盘事件:keydown 和 keyup - 图2“**KeyZ``**和其他按键码

键盘上不同的按键有不同的按键码。按键码可参考《UI 事件码规范》

例如:

  • 字符键的键码值,是“Key<letter>”的形式:例如“KeyA”、“KeyB”等。

  • 数字键的键码值,是“Digital<number>”的形式:例如“Digital0”、“Digital1”等。

  • 其他按键的键码值按照名字的不同而不同:例如“Enter”、“Backspace” 和“Tab”等。

有几种常规键盘布局,规范为每种布局都指定了对应的按键代码。

请参阅《规范的字母数字部分》以获得更多按键码信息。

键盘事件:keydown 和 keyup - 图3注意**大小写:是“**KeyZ**”,不是“****keyZ**

虽然很明显,但还是经常会犯错的地方。

注意,是“KeyZ”,不是“keyZ”!像 event.code === "keyZ" 这种检查是无效的。 “Key”必须是首字母大写的形式。

如果一个按键不代表任何字符呢?例如,ShiftF1 或其他一些,对于这类按键,event.keyevent.code 值基本一致。

按键 event.key event.code
F1 F1 F1
Backspace Backspace Backspace
Shift Shift ShiftRightShiftLeft

请注意,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 的值总是一致的,是于键盘上的按键唯一绑定的。

  1. document.addEventListener('keydown', function (event) {
  2. if (event.code === 'keyZ' && (event.ctrlKey || event.metaKey)) {
  3. alert('撤销');
  4. }
  5. });

自动重复

如果一个按键长时间按下不释放,就会重复触发 keydown 事件;当释放按键时,触发一次 keyup 事件。所以结果会看到触发许多的 keydown 事件和一个 keyup 事件。

所有重复 keydown 事件的事件对象的 event.repeat 属性值都为 true

默认行为

默认行为会有所不同,因为键盘操作可能会启动许多可能的行为。

例如:

  • 出现一个字符(最明显的输出了)。

  • 删除一个字符(按下 Delete 按键)。

  • 滚动页面(按下 PageDown 按键)。

  • 浏览器打开“保存”页面对话框(Ctrl+S)。

  • ……

阻止 keydown 事件的默认行为可以取消大多数(除个别操作系统级的快捷键外)上述行为的发生 。例如,Windows 系统里的 Alt+F4 会关闭当前浏览器窗口,这是使用 JavaScript 无法阻止的行为。

下例中,<input> 限制只能输入手机号码,不接受数字、+()- 外的其他符号:

  1. <script>
  2. function checkPhoneKey(key) {
  3. return (key >= '0' && key <= '9') || key === '+' || key === '(' || key === ')' || key === '-';
  4. }
  5. </script>
  6. <input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">

需要注意的是,在输入框中按下像 BackspaceLeftRightCtrl+V 等这样特殊的按键也变失效了,这是过滤算法 checkPhoneKey 带来的副作用。

我们放松一下规则:

  1. <script>
  2. function checkPhoneKey(key) {
  3. return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-' ||
  4. key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace';
  5. }
  6. </script>
  7. <input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">

现在箭头按键和删除按键都可以使用了。

但是我们仍然可以通过鼠标右键粘贴输入文本,所以这也不是百分之百可信赖的。我们可以先让它这样,因为多数场景下都是可行的。另一个可选方案是检查 input 事件,这个事件会在每次输入框内容修改后触,我们可以在处理器中检查新的值,无效的话就修改它。

遗留问题

过去还会使用 keypress 事件,还有事件对象上的 keyCodecharCodewhich 属性。

因为这些事件/事件属性并不是浏览器全兼容的,标准开发人员决定弃用它们。旧代码仍然有效,是因为浏览器还在支持它们,但是完全不需要再使用它们了。

曾几何时,本章中包含了对它们的详细描述,但现在我们可以忘记它们了。

总结

按下按键(可能是一个符号按键或是像 ShiftCtrl 这样的特殊按键)会触发键盘事件。唯一的例外是有时出现在笔记本键盘上的 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

  1. runOnKeys(
  2. () => alert("Hello!"),
  3. "KeyQ",
  4. "KeyW"
  5. );

答案

一、扩展热键

我们会用到两个事件处理器:document.onkeydowndocument.onkeyup

集合 pressed 中保存现在按下的按键。

第一个处理器用来添加按键,第二个处理器用来删除按键。每次 keydown 的时候,我们就检查下是否按下了所有想要按下的按键,如果是的话,就执行回调函数。

  1. function runOnKeys(func, ...codes) {
  2. let pressed = new Set();
  3. document.addEventListener('keydown', function(event) {
  4. pressed.add(event.code);
  5. for (let code of codes) { // 是否按下了所有想要按下的按键
  6. if (!pressed.has(code)) {
  7. return;
  8. }
  9. }
  10. // 好的,都按下了
  11. // 在 alert 的时候,如果用户释放了按键
  12. // JavaSript 不会接收到"keyup"事件
  13. // pressed 集合里仍然保留所有按下的按键信息
  14. // 为了避免"粘性"按键,我们重置了状态(清空了按键集合)
  15. // 如果用户想要再一次执行热键回调,就需要再一次全部按下按键
  16. pressed.clear();
  17. func();
  18. });
  19. document.addEventListener('keyup', function(event) {
  20. pressed.delete(event.code);
  21. });
  22. }

(完)