做个快捷键功能才发现:key 和 code 完全是两码事

举报
柠檬-夏日清爽 发表于 2026/09/29 15:55:11 2026/09/29
【摘要】 给页面加快捷键,想监听"按了回车"。结果用 keyCode 判断在别的键盘布局下全乱了,用 key 判断中文输入法下又触发了提交。这篇讲 KeyboardEvent 的三个属性 key / code / keyCode 到底差在哪、怎么正确判断组合键(Ctrl/Cmd/Shift/Alt)、以及怎么绕开输入法假提交这个大坑。

给编辑器加个快捷键:按 Ctrl/Cmd + S 保存,Esc 退出,Enter 提交。

写着写着发现三件事都对不上:在 Mac 上 Ctrl 不生效、用中文输入法打字时按回车居然触发了提交、换了键盘布局 KeyA 的位置也不对。

根子都在 KeyboardEvent 那几个属性上。

一、key / code / keyCode 是三个东西

一个"按键"事件里有三个看起来很像的字段,含义完全不同:

属性 含义 例:按下字母 A 例:按下回车
key 实际产生的"字符" "a" / "A"(随 Shift 变) "Enter"
code 物理按键位置 "KeyA"(不随布局变) "Enter"
keyCode 老式数字编码(已废弃) 65 13

差别在哪:

  • key 是"输出了什么字符"。按 A 配 Shift 就是 "A",不配就是 "a"。它会随输入法和大小写变。
  • code 是"按了哪个物理键"。不管你键盘什么布局,KeyA 永远是左边那排第三个字母键。它不关心大小写、不关心字符。
  • keyCode 是上古时代的数字,各浏览器曾经不一致,规范已经标废弃,新代码别再用。

所以判断"用户按了保存"该用哪个?看场景:

// 判断"回车"——用 key 最直观
if (e.key === 'Enter') { ... }

// 判断"按了 A 键这个位置"——用 code(游戏、乐器映射常用)
if (e.code === 'KeyA') { ... }

二、组合键怎么判断

Ctrl / Cmd / Shift / Alt 不在 key 里单独给,而是放在四个布尔属性上:

document.addEventListener('keydown', (e) => {
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
    e.preventDefault()          // 拦掉浏览器默认的"保存网页"
    save()
  }
})

要点:

  • metaKey 是 Mac 的 ⌘(Command),ctrlKey 是 Windows/Linux 的 Ctrl。跨平台快捷键要两个都判断,否则 Mac 用户按 ⌘S 没反应。
  • e.key === 's' 用小写 s。组合键里的字母键,无论有没有按 Shift,key 都是小写字母("s"),别写成 "S"。
  • preventDefault() 别忘了。像 Ctrl+S、Ctrl+P、Ctrl+W 这些,浏览器有默认行为(保存网页、打印、关标签页),不拦就会触发默认动作。
  • e.key 对组合键的主键也可能是 "S" 之外的。比如 Ctrl + 数字 的 key 是 "1" 不是 Digit1,用 key 判断更稳。

三、输入法假触发的坑

这是做编辑器最容易翻车的地方。

用户在中文输入法里打字,打到一半按了回车选词——这个回车也会冒出 keydown,e.key === 'Enter' 为真。如果你在这时候提交表单,用户字还没打完就被发了出去。

解决:用 e.isComposing 判断。输入法组合输入过程中,这个属性是 true,组合结束才变 false:

input.addEventListener('keydown', (e) => {
  if (e.isComposing) return          // 正在用输入法组字,忽略
  if (e.key === 'Enter') submit()
})

更古老的做法是监听 compositionend,但 isComposing 一个判断就够,优先用它。

四、几个容易忽略的点

  • keydown 会重复触发。按住一个键不松,系统会持续发 keydown(像打字时字母一直冒)。做"按一次执行一次"的功能(比如游戏跳跃),要么用 keyup,要么自己加个"按下了没松开"的标志位去重:

    const pressed = new Set()
    document.addEventListener('keydown', (e) => {
      if (pressed.has(e.code)) return     // 已经在按住,忽略重复
      pressed.add(e.code)
      ...
    })
    document.addEventListener('keyup', (e) => pressed.delete(e.code))
    
  • 有些键 preventDefault 没用。Ctrl+W、Ctrl+T 这类标签页级操作,浏览器不让网页拦,拦了也白拦。涉及这些的快捷键得避开。

  • Esc 不一定弹出来。全屏模式、弹窗里 Esc 可能已被系统占用了行为,别把"退出"只绑在 Esc 上。

  • 只给可聚焦元素监听。在一个没焦点的 div 上加 keydown 可能收不到事件,通常挂在 document 或具体输入框上。

  • code 在笔记本小键盘上不一样。外接数字小键盘的 1 是 Numpad1,不是 Digit1,要做数字快捷键得两个都判断,或者干脆用 key。

小结

快捷键翻车基本都栽在 KeyboardEvent 那三个属性上:key 是输出了什么字符(随 Shift/输入法变)、code 是按了哪个物理键(不随布局变)、keyCode 已废弃别用。跨平台组合键用 (ctrlKey || metaKey) 一起判断、记得 preventDefault;字母键用小写比对。最大的坑是输入法选词时的回车会假触发提交,用 e.isComposing 拦掉就稳了。把这几条记牢,一个不出怪事的快捷键功能就成了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。