
JavaScript键盘事件详解:key、code、keyCode的区别与调试方法
📷 Life Of Pix / PexelsJavaScript键盘事件详解:key、code、keyCode的区别与调试方法
JavaScript键盘事件实用指南 — 解释keyCode为何被弃用、key与code的区别,以及如何构建跨浏览器、跨键盘布局的可靠快捷键。
这个bug我遇到过不止一次:在Chrome里写好键盘快捷键处理器,测试没问题,发布上线,然后就收到Firefox用户的bug反馈说快捷键不工作。同样的代码,不同的行为。挖了一个小时才找到原因 — 用了e.keyCode来检测按键,而它在不同浏览器和键盘布局下返回的值不一样。
这个bug完全可以避免。这也是为什么正确理解JavaScript键盘事件值得投入时间。本文将介绍完整的键盘事件模型 — 哪些事件会触发、应该使用哪些属性、哪些要避免,以及如何构建在所有地方都能正常工作的快捷键。
三个事件:keydown、keypress、keyup
用户按下键时,浏览器会按顺序触发最多三个事件:
- keydown — 键按下时立即触发,在字符插入之前
- keypress — keydown之后触发,仅适用于产生字符的键(字母、数字、标点)
- keyup — 键松开时触发
简单来说:大多数情况下使用keydown。原因如下:
keypress已被弃用。它不会对Escape、Delete、F1-F12或方向键等非打印键触发。如果你的快捷键处理器只对字母键有效,keypress很可能就是原因。新代码中不要使用它。
keyup在你需要在键松开后才响应的场景有用 — 比如用户输入字符后更新预览。但对于快捷键和游戏控制,keydown提供更快、更可预测的行为。
keydown还有一个优势:长按键时会以操作系统的键盘重复速率持续触发,这正是滚动或移动游戏角色所需要的。
document.addEventListener('keydown', (e) => {
// 这里才是正确的位置
console.log(e.key, e.code);
});
真正重要的属性
KeyboardEvent触发时,事件对象包含许多属性。以下是你需要了解的内容。
key — 键产生的值
key返回当前上下文中键所代表的字符串值。对于普通字母键,就是字符本身:"a"、"A"(按住Shift时)、"é"(法语键盘)。对于特殊键,是描述性名称:"Enter"、"Escape"、"ArrowLeft"、"F5"、"Backspace"。
这是你应该首先考虑的属性。它感知布局和修饰键,能告诉你这个键对用户来说意味着什么。
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
submitForm();
}
if (e.key === 'Escape') {
closeModal();
}
if (e.key === 'ArrowLeft') {
goToPreviousSlide();
}
});
有一点要注意:key区分大小写和修饰键。不按Shift按a键得到"a",按Shift则得到"A"。如果快捷键不区分大小写,需要规范化处理:
if (e.key.toLowerCase() === 'k') {
openCommandPalette();
}
code — 按下了哪个物理键
code返回物理按键位置的标识符,与修饰键或键盘布局无关。字母区左上角的键始终是"KeyQ" — 即使用户的布局在那里放的是"A"(如AZERTY布局)。
格式一致:字母键是"KeyA"到"KeyZ",数字键是"Digit0"到"Digit9",功能键是"F1"到"F12"。
当你关心键的物理位置而不是它产生的字符时使用code。经典用例是游戏控制:
document.addEventListener('keydown', (e) => {
switch (e.code) {
case 'KeyW':
case 'ArrowUp':
player.moveUp();
break;
case 'KeyS':
case 'ArrowDown':
player.moveDown();
break;
case 'KeyA':
case 'ArrowLeft':
player.moveLeft();
break;
case 'KeyD':
case 'ArrowRight':
player.moveRight();
break;
}
});
使用code意味着无论玩家使用QWERTY、AZERTY还是Dvorak键盘,WASD控制都以相同方式工作。在AZERTY上,W键在不同位置,所以e.key会返回"z" — 但e.code仍然返回"KeyW",因为物理位置相同。
keyCode和which — 遗留属性,已弃用,请避免使用
keyCode和which是旧的方式。它们返回键的数字代码 — A是65,Enter是13,Escape是27。问题在于这些值不一致,特别是对于标点符号和特殊键。不同浏览器返回不同的数字,而且这些值基于Windows虚拟键码,毫无直觉可言。
MDN文档将keyCode和which都标记为已弃用。它们仍然存在是为了向后兼容,但新代码中不要使用它们。
常见的旧代码:
// 不要这样做
if (e.keyCode === 13) { /* Enter */ }
if (e.which === 27) { /* Escape */ }
现代写法:
// 应该这样做
if (e.key === 'Enter') { /* Enter */ }
if (e.key === 'Escape') { /* Escape */ }
charCode — 仅在keypress中,同样已弃用
charCode返回字符的Unicode码点,但它只在keypress事件中、只对可打印字符有效。由于keypress本身已被弃用,charCode也双重弃用了。直接使用e.key,需要Unicode值时调用.codePointAt(0)。
location — 区分多处出现的键
location告诉你在键盘多个位置存在的键是从哪里被按下的。是一个数字:0是标准位置,1是左侧修饰键,2是右侧修饰键,3是数字键盘。
e.location === 1且e.key === "Shift"表示按的是左Shift键。e.location === 3且e.key === "5"表示按的是数字键盘上的5。很少需要用到,但知道它存在有好处。
修饰键:Ctrl、Shift、Alt、Meta
键盘快捷键通常是普通键与一个或多个修饰键的组合。事件对象有每个修饰键的布尔属性:
e.ctrlKey— Ctrl键被按住e.shiftKey— Shift键被按住e.altKey— Alt(Mac上是Option)键被按住e.metaKey— Meta键被按住(Mac上是Command,Windows上是Windows键)
document.addEventListener('keydown', (e) => {
// Ctrl+S(Mac上是Cmd+S)
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault(); // 阻止浏览器的保存对话框
saveDocument();
}
// Ctrl+Shift+P — 命令面板
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'p') {
e.preventDefault();
openCommandPalette();
}
// Alt+Left — 返回
if (e.altKey && e.key === 'ArrowLeft') {
goBack();
}
});
e.ctrlKey || e.metaKey模式是跨平台快捷键的标准做法。macOS上大多数快捷键使用Command(metaKey),Windows/Linux使用Ctrl。这个模式同时处理两种情况。
注意e.preventDefault()的调用。如果你的快捷键与浏览器快捷键冲突,需要调用它来阻止浏览器执行该操作。否则Ctrl+S会打开浏览器的保存对话框。
国际键盘问题
有一个经常困扰开发者的场景:你在应用中添加了键盘快捷键,比如Ctrl+/用于切换注释。在en-US键盘上测试,完美运行。然后德国用户提交了一个bug报告 — 快捷键不工作。
德国键盘布局没有专用的/键。它通过Shift+7或其他组合输入。所以按下那个键位时,e.key的值与你预期的完全不同。
两种处理方法:
方法一:位置相关的快捷键使用e.code
// 美式键盘上的/键在特定物理位置
// code对那个位置始终返回'Slash'
if (e.ctrlKey && e.code === 'Slash') {
toggleComment();
}
方法二:让用户自己配置快捷键
VS Code的做法 — 让用户将任何快捷键重新绑定到适合自己的键位组合。实现更复杂,但对于面向国际用户的专业工具来说是正确答案。
无障碍访问:键盘导航很重要
为应用添加自定义键盘交互时,不要忘记键盘导航是核心无障碍要求。依赖屏幕阅读器或无法使用鼠标的用户依赖键盘访问。
几点需要记住:
焦点管理。 确保交互元素可以获得焦点(必要时使用tabindex="0"),并且随着UI变化焦点逻辑移动。打开模态框时,将焦点移入其中。关闭时,将焦点移回触发它的元素。
不要困住焦点(模态框除外)。用户应该能够用Tab键遍历界面而不被卡住。
不要只依赖键盘快捷键。 用户可能发现不了。为每个操作提供可见的UI替代方案。
使用Keycode Viewer进行调试
在实际构建键盘交互时,最困难的部分之一往往是搞清楚浏览器对特定按键返回什么值。文档很有帮助,但有时你就是需要按下那个键然后看看输出。
这正是我们的Keycode Viewer工具的用途。打开工具,按任意键,它会立即显示:
key— 逻辑值code— 物理位置标识符keyCode— 遗留数字值(处理旧代码时参考用)which— 另一个遗留属性charCode— keypress事件的字符码location— 标准/左/右/数字键盘ctrlKey、shiftKey、altKey、metaKey— 修饰键状态
处理国际键盘、特殊字符时,或者排查为什么现有代码无法识别某个特定键时特别有用。不用添加console.log语句然后重新加载,只需打开工具按下键即可。
快速参考
| 属性 | 使用场景 | 已弃用? |
|---|---|---|
key | 字符或动作名称 | 否 |
code | 物理键位置 | 否 |
keyCode | 无 — 仅限遗留代码 | 是 |
which | 无 — 仅限遗留代码 | 是 |
charCode | 无 — 仅限遗留代码 | 是 |
ctrlKey | 检测Ctrl是否按住 | 否 |
shiftKey | 检测Shift是否按住 | 否 |
altKey | 检测Alt/Option是否按住 | 否 |
metaKey | 检测Cmd/Win键是否按住 | 否 |
核心思维模型:先用e.key。它告诉你键的含义。只有当你明确关心物理位置时(游戏控制、布局无关的快捷键),才使用e.code。新代码中永远不要使用keyCode、which或charCode。
掌握这些,你的键盘交互就能在各种浏览器、键盘布局和操作系统上保持一致。来自Firefox用户的bug报告也会随之消失。