键盘测试

在浏览器里点亮 104 键屏幕键盘,逐个显示每一次按键的 KeyboardEvent.code、.key、keyCode 与 location,并统计峰值同时按键数来判断键盘是 6KRO 还是 NKRO。

F 区(16 键)

EscF1F2F3F4F5F6F7F8F9F10F11F12PrtScScrLkPause

主键区(61 键)

`1234567890-=Backspace
TabQWERTYUIOP[]\
CapsASDFGHJKL;'Enter
ShiftZXCVBNM,./Shift
CtrlMetaAltSpaceAltMetaMenuCtrl

编辑区(6 键)+ 方向键(4 键)

InsHomePgUpDelEndPgDn
↑←↓→

小键盘(17 键)

Num/*-
789+
456Enter
1230.
事件总数14
不同按键6
当前按下0
峰值同时按下2

峰值 2 键 ≤ 阈值 6 → 还没触到上限。想确认是不是全键无冲,请同时按住 7 个以上字母键再试。

event.typekeydown
KeyboardEvent.codeKeyA
KeyboardEvent.key"A"
keyCode(已废弃)65
location0 标准
repeat(长按)false
锁定键—

提示:下表是页面预置的 14 条示例事件(涉及 6 个不同 code,峰值同时按下 2 键),只要你真按下一个键就会自动清空并转入实测。窗口必须处于焦点;在输入框里打字不会触发高亮。

事件记录(默认显示前 12 条)

#事件codekeykeyCodelocationrepeat
1keydownKeyAa650 标准false
2keyupKeyAa650 标准false
3keydownShiftLeftShift161 左false
4keydownKeyAA650 标准false
5keyupKeyAA650 标准false
6keyupShiftLeftShift161 左false
7keydownDigit11490 标准false
8keydownNumpad11973 小键盘false
9keyupNumpad11973 小键盘false
10keyupDigit11490 标准false
11keydownArrowLeftArrowLeft370 标准false
12keyupArrowLeftArrowLeft370 标准false
13keydownSpace(空格)320 标准false
14keyupSpace(空格)320 标准false

按键对照表(默认显示前 12 行)

codekey(未按 Shift)key(按住 Shift)旧 keyCodelocation
KeyAaA650 标准
KeyZzZ900 标准
Digit11!490 标准
Digit00)480 标准
Numpad111973 小键盘
Numpad000963 小键盘
Minus-_1890 标准
Equal=+1870 标准
BracketLeft[{2190 标准
BracketRight]}2210 标准
Backslash\|2200 标准
Semicolon;:1860 标准
Quote'"2220 标准
Comma,<1880 标准
Period.>1900 标准
Slash/?1910 标准
Backquote`~1920 标准
Space(空格)(空格)320 标准
EnterEnterEnter130 标准
NumpadEnterEnterEnter133 小键盘
ShiftLeftShiftShift161 左
ShiftRightShiftShift162 右
ControlLeftControlControl171 左
MetaLeftMetaMeta911 左

使用方法

  1. 打开页面就能看到一整块 104 键的屏幕键盘,按 US ANSI 布局排布:F 区 16 键、主键区 61 键、编辑区 6 键、方向键 4 键、小键盘 17 键,每颗键帽都带自己的 code 值。
  2. 在浏览器窗口里按下一个键,屏幕上对应的键帽立刻亮蓝,松手熄灭。右侧「本次事件」面板同步刷新出这次按键的 code、key、keyCode、location、repeat 与锁定键状态。
  3. 同时按住多个键不松手,观察「峰值同时按下」这张卡片。它是判断键盘无冲档位的唯一依据,默认阈值 6 来自 USB 引导协议一次最多报告 6 个非修饰键。
  4. 峰值超过阈值时,下方判定行会给出「大概率 NKRO」的结论。想调严或放松,改「无冲判定阈值」(1~20 的整数,默认 6),在输入框里直接按 Enter 立刻重新判定。
  5. 事件记录表默认只渲染前 12 条,点「加载更多」展开全部;「复制记录」按 TSV 复制,「导出记录 CSV」下载带 BOM 的文件,粘进 Excel 不会乱码。
  6. 页面预置的 14 条示例只是排版演示,你第一次按下真实按键时它们会被自动清空;想手动重来点「清空记录并开始实测」。

键盘测试能解决什么问题

一是定位「这个键到底坏没坏」。同一颗键可能被吃掉三次:键盘固件没发、操作系统驱动拦截、浏览器或输入法提前 preventDefault。本工具只看浏览器最终收到的 keydown:按住某颗键屏幕键盘毫无反应而其他键正常,问题基本在驱动或固件层;只有某个网页里没反应,那多半是那个页面自己拦掉了。

二是判断键盘是几键无冲。游戏里「按三个键就有一个失灵」几乎都是 6KRO 或 2KRO 的键位冲突。峰值同时按下数一旦超过 6,就说明键盘实打实地超出了 USB 引导协议上限,走的是 NKRO 报文。

三是查出「卡键」。「当前按下」如果在松手后没回到 0,说明浏览器少收到了一次 keyup。常见诱因是按了系统级组合键、切窗口时松手、或录入途中弹出了输入法候选框,实测中这类漏事件比想象中多。

四是给前端快捷键选对字段。写「WASD 移动」「按 E 交互」必须用 code,否则德语键盘的 Z 会跑到 Y 的位置、法语 AZERTY 上 WASD 直接错位;判断「用户输入了哪个字符」则必须用 key。选错的典型症状是「本地好用、海外用户说方向不对」。

五是区分左右修饰键。ShiftLeft 与 ShiftRight 的 key 都是 "Shift"、keyCode 都是 16,只有 code 和 location 能把它们分开,做游戏键位映射必须处理。

这个键盘测试不做这些事:不测键盘回报率、不测按键延迟(那是反应时间测试的活)、不读固件与型号、不评估手感与噪音。

原理和变量:物理位置、字符与三套编码

这个键盘测试监听两个最基础的事件 keydown 与 keyup,字段定义跟的是 W3C UI Events 规范里的 KeyboardEvent 接口。下面七条是它真正在用的机制。

① code 是物理位置,key 是当前字符。code 按「US 布局下的那颗键」命名,与你的系统键盘布局无关;key 则经过布局表和修饰键换算,是这次按键实际产生的字符。按住 A 键,code 恒为 "KeyA"、key 是 "a";再按住左 Shift 敲同一个键,code 依然是 "KeyA"、key 已变成 "A"。把键盘切成 AZERTY 之后,物理上同一颗键的 code 还是 "KeyA",key 会变成 "q"——这就是「物理位置」与「字符」的分界线。

② 小键盘与主键盘的分歧。小键盘 1 的 code 是 "Numpad1",主键盘 1 的 code 是 "Digit1",但两者的 key 都是 "1";同理 NumpadEnter 与 Enter 的 key 都是 "Enter"。只用 key 时这两组键完全不可分辨。

③ keyCode 已废弃但值仍在。按 UI Events 规范它是 legacy 属性,规范只要求兼容不保证取值。实测规律:A–Z 等于大写字母 ASCII(65~90),数字行 0–9 等于 48~57,小键盘 0–9 等于 96~105,两套编号不重叠。注意小写 a 的 keyCode 也是 65 而不是 97,绝不能拿它反推字符。

④ location 把位置拆成四档。0 是标准位(字母键),1 是左侧(ShiftLeft、ControlLeft、AltLeft、MetaLeft),2 是右侧,3 是小键盘。它是 code 之外唯一能说明「在哪一边」的字段。

⑤ repeat 与系统重复。按住不放时第二个及以后的 keydown 会带 event.repeat === true,且不再产生新的 keyup。节奏由操作系统决定而非浏览器:Windows 的重复延迟分 0~3 四档,约 250 / 500 / 750 / 1000 ms,默认档对应约 500 ms;重复速率分 0~31 档,约合 2.5~30 次/秒,默认约 30 次/秒即每 33 ms 一次。所以本页「不同按键」按 code 去重,「事件总数」按事件累加,长按会让后者飞涨而前者不动。

⑥ preventDefault 抑制默认行为。空格与上下左右方向键默认会滚动页面,Tab 会移动焦点,Firefox 里斜杠会打开「快速查找」、单引号会打开「仅链接快速查找」,Alt 会把焦点抢到菜单栏。不拦的话按一下空格整页就跳走,键盘图根本看不清。本工具对空格、方向键、Tab、F1、F3、F7、斜杠、单引号、退格、翻页键与 Home/End 一律调用 preventDefault()。

⑦ 无冲档位怎么得出的。USB HID 键盘的引导协议报文固定 8 字节:第 0 字节是修饰键位图,第 1 字节保留,第 2~7 字节是 6 个键码槽。也就是标准引导协议一次最多报告 6 个同时按下的非修饰键,这就是 6KRO 的由来;修饰键另走 8 位位图,能表达 2^8 = 256 种组合,不受 6 键限制。要报告更多键就得改用厂商自定义的 NKRO 字段。本页把「峰值同时按下 > 阈值 6」判为「大概率 NKRO」;反过来峰值不超过 6 并不能证明它是 6KRO,也可能只是你还没按够键。

⑧ 什么时候拿不到有效 code。手机与平板的虚拟键盘会把 code 报成空字符串;输入法组字期间事件带 isComposing === true、key 常为 "Process";法语、西班牙语键盘上的重音死键 key 是 "Dead";笔记本的 Fn 键、音量与亮度键由固件直接处理,多数根本不产生 KeyboardEvent,页面自然测不到。

完整示例(数值仅作演示)

页面打开时的默认状态与正文完全咬合:屏幕键盘画出 104 颗键、阈值输入框预填 6、记录表预置 14 条示例事件(涉及 6 个不同 code,峰值同时按下 2 键,所以判定行显示「峰值 2 键 ≤ 阈值 6」)。按这套默认值走一遍:

  1. 按一下 A 键:面板显示 keydown、code 为 "KeyA"、key 为 "a"、keyCode 为 65、location 为 0 标准;松手后多出一条 keyup。这就是示例表第 1~2 行。
  2. 按住左 Shift 再敲同一个键:code 仍为 "KeyA",key 变 "A",keyCode 依然是 65,location 变 1 左(第 3~6 行)。
  3. 敲主键盘 1 再敲小键盘 1:两条记录 key 都是 "1",code 却是 "Digit1" 与 "Numpad1",keyCode 分别是 49 与 97,location 分别是 0 与 3 小键盘(第 7~10 行)。
  4. 把左手五指压在同一排字母键上:峰值会依次爬到 7。7 > 6,判定行随即变成「已超出 USB 引导协议上限,基本可判定走 NKRO 报文」。
  5. 此时「当前按下」显示 7;逐根手指抬起,数字逐一回落,全部松开后回到 0,说明没有丢 keyup。

把这次实测导成 CSV,前几行形如(列顺序 # / 事件 / code / key / keyCode / location / repeat):

1,keydown,KeyA,a,65,0 标准,false
2,keyup,KeyA,a,65,0 标准,false
3,keydown,ShiftLeft,Shift,16,1 左,false
4,keydown,KeyA,A,65,0 标准,false

输入范围与按键对照表

本页只有一个可填的输入框「无冲判定阈值」,允许 1~20 的整数,默认 6,步长 1。低于 1、高于 20、小数、字母、留空都会被拒绝,并给出指名字段与范围的具体提示,不会静默改成默认值。下面的按键对照表列出 24 组最常见的 code,默认显示 12 行,「加载更多」展开到全部,「复制对照表」可整表按 TSV 粘进表格软件。

codekeykeyCodelocation含义
KeyAa650 标准字母键,code 与字符完全解耦
Digit11490 标准主键盘数字,受 Shift 影响
Numpad11973 小键盘与 Digit1 的 key 相同、code 不同
NumpadEnterEnter133 小键盘与 Enter 的 key 相同、code 不同
ShiftLeftShift161 左只能靠 code / location 区分左右
ControlLeftControl171 左修饰键,走报文第 0 字节位图
AltLeftAlt181 左Windows 上默认会抢菜单焦点
MetaLeftMeta911 左Win 键 / macOS 的 Command
Space(空格)320 标准必须 preventDefault,否则滚动
EnterEnter130 标准与 NumpadEnter 的 keyCode 相同
TabTab90 标准默认移动焦点,本页已拦截
EscapeEscape270 标准不受 Shift 影响,可做复位键

看这张表要注意两点。第一,左右修饰键的区别只在 code 与 location:ShiftLeft、ShiftRight 的 key 与 keyCode 完全相同。第二,只有 keyCode 是「不保证」的字段,同一颗物理键在不同浏览器或系统语言下可能给出不同数字,正式项目里可以拿它做兼容兜底,但不该当唯一判据,更不该拿它反推字符。

边界与精度说明

常见问题

为什么我按 A,显示的是 code = KeyA、key = a,两者为什么要不一样?

因为这两个字段回答的是两个不同问题。code 回答「你按的是哪一颗物理键」,用 US 布局命名、不随系统布局变化;key 回答「这次按键产生了什么字符」,会跟着布局和 Shift 一起变。做游戏方向键、快捷键注册时你要的是前者,做输入内容判断时你要的是后者。

小键盘的 1 和主键盘的 1 为什么 code 不一样?

Numpad1 与 Digit1 是两颗彼此独立的物理键,所以 code 不同;但它们的 key 都是 "1",location 分别是 3 小键盘与 0 标准。银行录入、POS 结算这类场景常要求两个数字键区互不干扰,此时必须用 code 判断。

怎么确认键盘是不是全键无冲?

把「无冲判定阈值」保持默认 6,然后同时按住 7 颗以上字母键不要松手。峰值超过 6 时判定行会给出「大概率 NKRO」;如果按住 8 颗却只显示 6,说明键盘确实走引导协议,多出来的键被丢掉了。这项结果与操作系统无关,只反映键盘与 USB 主机之间的报文能力。

按住一个键不放,为什么事件总数一直在涨?

那是操作系统的按键重复。长按超过重复延迟(Windows 默认约 500 ms)后,系统会按重复速率(默认约 30 次/秒,即每 33 ms 一次)持续发出带 event.repeat === true 的 keydown。这些重复事件只累加「事件总数」,不增加「不同按键」,也不改变峰值。

为什么在这个页面上按空格,页面不往下滚了?

因为本工具对空格调用了 preventDefault()。不拦截的话一次空格会把页面滚走一整屏,键盘图就看不见了。同理被拦下的还有方向键、Tab、F1、F3、F7、斜杠、单引号、退格与翻页键。

我在输入框里打数字,为什么屏幕键盘没有高亮?

这是刻意跳过的:阈值输入框需要正常的输入光标、选区和删除行为,拦下来就没法编辑了。所以本页对所有 input 与 textarea 上的键盘事件直接放行,不做高亮、不计数、不拦截。

左右两个 Shift 能分开吗?

能。它们的 key 都是 "Shift"、keyCode 都是 16,光看这两个字段完全不可分;但 code 是 "ShiftLeft" 与 "ShiftRight",location 是 1 左与 2 右。左侧的 Ctrl、Alt、Meta 同理。

Fn 键、音量键、亮度键为什么按了没反应?

这些键通常由键盘固件直接处理:Fn 在多数键盘上根本不通过 USB 上报,音量与亮度键则以消费类控制设备的形式出现在系统里,而不是标准键盘报文。它们大多不产生带 code 的 KeyboardEvent,所以任何网页都测不到。

相关工具