最常见的原因是事件未派发到监听节点,因浏览器只向焦点元素发送keydown事件;初始时document.body常无焦点,需主动调用focus(),并检查readonly、disabled、contenteditable等状态。

为什么 document.addEventListener('keydown') 有时收不到按键
最常见原因不是代码写错了,而是事件根本没派发到你监听的节点上。浏览器只把 keydown 事件发给当前获得焦点(focus)的元素——如果页面加载完 document.body 没自动聚焦,又没其他可聚焦元素抢走焦点,那全局监听就静默失效。
典型现象:刚打开页面按空格没反应,点一下空白处再按就正常了。说明初始时 document.body 没 focus。
- 对
document监听前,加一句document.body.focus()(仅限需要全局响应的场景) - 确认目标元素没被
readonly或disabled禁用,否则不会触发keydown - 检查是否有
contenteditable="true"的区域在页面加载后自动抢了焦点
event.key 和 event.code 到底该用哪个
event.key 表示“用户想输入什么”,受键盘布局、Shift/CapsLock 状态影响;event.code 表示“物理按键位置”,固定不变。混用会导致逻辑错乱。
比如美式键盘按左下角 Ctrl 键:event.code 永远是 'ControlLeft',而 event.key 在按下时是 'Control',松开后可能是 'Unidentified'。
立即学习“前端免费学习笔记(深入)”;
- 做全局快捷键(Ctrl+S 保存):用
event.ctrlKey && event.key === 's' - 做游戏方向键映射(WASD 或方向键都支持):用
event.code === 'ArrowUp' || event.code === 'KeyW' - 判断字母/数字键:优先看
event.key(它自动处理大小写) - 判断 Esc、F1、Backspace、方向键、修饰键状态:优先看
event.code或event.location - 绝对不要依赖
event.keyCode或event.which——已废弃,Chrome/Firefox 新版本可能返回 0
tabindex="0" 加了但键盘还是进不去?
加 tabindex="0" 只解决“能进 tab 流”,不等于“能操作”或“有反馈”。很多自定义控件卡在这一步。
常见错误:给一个 div 加了 onclick 和 tabindex="0",但没配 role="button",也没监听 keydown 响应空格/回车——键盘用户 Tab 过去后按空格毫无反应。
- 原生可聚焦元素(
button、a[href]、input)**不要加**tabindex="0"——冗余且可能干扰旧浏览器 - 自定义按钮/菜单根容器(如
<div role="button">)**必须加**tabindex="0",否则键盘根本到不了 - 加完之后必须手动监听
keydown,判断event.key === 'Enter'或event.key === ' '(注意是空格字符)并触发行为 - 如果 CSS 清除了
outline,必须补上:focus-visible样式,否则焦点位置不可见
模态框焦点锁不住,键盘用户一按 Tab 就跑出去
焦点囚笼不是靠一个 .focus() 就能搞定的。漏掉任一环节,键盘用户都会迷失。
关键动作有两个:打开时自动聚焦第一个可操作项;关闭时焦点回到触发源。中间还得拦截外部焦点尝试。
- 模态框打开后,立刻对第一个可聚焦元素(如确认按钮)调用
.focus(),前提是它有tabindex="-1"(button元素本身可聚焦,无需加) - 监听
focusin事件,一旦检测到焦点试图移出模态框,立刻event.preventDefault()并把焦点切回模态框内最后一个/第一个可聚焦元素 - 模态框关闭时,用
triggerElement.focus()把焦点还回去——这个triggerElement必须在打开前缓存,不能靠 DOM 查询临时找 - 注意:元素若被
display: none或visibility: hidden隐藏,.focus()会静默失败
真正麻烦的不是写几行监听代码,而是每个交互点都要同步考虑「能否被 Tab 到」「按空格/回车是否响应」「焦点样式是否可见」「焦点离开后是否可追溯」——这四件事缺一不可。漏掉任意一个,对键盘用户就是断点。



















