ToolPal
计算机键盘特写

JavaScript键盘事件详解:key、code、keyCode的区别与调试方法

📷 Life Of Pix / Pexels

JavaScript键盘事件详解:key、code、keyCode的区别与调试方法

JavaScript键盘事件实用指南 — 解释keyCode为何被弃用、key与code的区别,以及如何构建跨浏览器、跨键盘布局的可靠快捷键。

2026年4月9日3分钟阅读

这个bug我遇到过不止一次:在Chrome里写好键盘快捷键处理器,测试没问题,发布上线,然后就收到Firefox用户的bug反馈说快捷键不工作。同样的代码,不同的行为。挖了一个小时才找到原因 — 用了e.keyCode来检测按键,而它在不同浏览器和键盘布局下返回的值不一样。

这个bug完全可以避免。这也是为什么正确理解JavaScript键盘事件值得投入时间。本文将介绍完整的键盘事件模型 — 哪些事件会触发、应该使用哪些属性、哪些要避免,以及如何构建在所有地方都能正常工作的快捷键。

三个事件:keydown、keypress、keyup

用户按下键时,浏览器会按顺序触发最多三个事件:

  1. keydown — 键按下时立即触发,在字符插入之前
  2. keypress — keydown之后触发,仅适用于产生字符的键(字母、数字、标点)
  3. keyup — 键松开时触发

简单来说:大多数情况下使用keydown。原因如下:

keypress已被弃用。它不会对EscapeDeleteF1-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",因为物理位置相同。

keyCodewhich — 遗留属性,已弃用,请避免使用

keyCodewhich是旧的方式。它们返回键的数字代码 — A65,Enter是13,Escape是27。问题在于这些值不一致,特别是对于标点符号和特殊键。不同浏览器返回不同的数字,而且这些值基于Windows虚拟键码,毫无直觉可言。

MDN文档将keyCodewhich都标记为已弃用。它们仍然存在是为了向后兼容,但新代码中不要使用它们。

常见的旧代码:

// 不要这样做
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 === 1e.key === "Shift"表示按的是左Shift键。e.location === 3e.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 — 标准/左/右/数字键盘
  • ctrlKeyshiftKeyaltKeymetaKey — 修饰键状态

处理国际键盘、特殊字符时,或者排查为什么现有代码无法识别某个特定键时特别有用。不用添加console.log语句然后重新加载,只需打开工具按下键即可。

快速参考

属性使用场景已弃用?
key字符或动作名称
code物理键位置
keyCode无 — 仅限遗留代码
which无 — 仅限遗留代码
charCode无 — 仅限遗留代码
ctrlKey检测Ctrl是否按住
shiftKey检测Shift是否按住
altKey检测Alt/Option是否按住
metaKey检测Cmd/Win键是否按住

核心思维模型:先用e.key。它告诉你键的含义。只有当你明确关心物理位置时(游戏控制、布局无关的快捷键),才使用e.code。新代码中永远不要使用keyCodewhichcharCode

掌握这些,你的键盘交互就能在各种浏览器、键盘布局和操作系统上保持一致。来自Firefox用户的bug报告也会随之消失。

常见问题

分享文章

XLinkedIn

相关文章