Esc关闭对话框必须监听keydown事件,动态绑定/解绑document监听器,检查e.key==='Escape'并preventDefault,关闭后用setTimeout延迟还原焦点,同时支持Cmd+.跨平台关闭。

Esc 退出必须监听 keydown,不是 keyup 或 keypress
按 Esc 关闭对话框的响应必须在按键按下瞬间生效,keydown 是唯一能稳定捕获该行为的事件。用 keyup 会延迟到松开才触发,用户已移开手指,体验断裂;keypress 根本不触发 Esc 键,且已被浏览器废弃。
常见错误现象:点击对话框后按 Esc 没反应,或要连按两下才关闭——本质是监听了错误事件类型,或监听节点未获得焦点。
- 始终在
document上监听:document.addEventListener('keydown', handler),避免因对话框内元素失焦导致事件丢失 - handler 中第一行就检查
e.key === 'Escape',别用e.code === 'Escape'(虽也对,但key更语义清晰且兼容性无差别) - 必须加
e.preventDefault(),否则某些浏览器可能同时触发系统级 Esc 行为(如关闭全屏、退出视频播放)
只在对话框打开时响应 Esc,避免全局误触
不能让页面任何时刻都响应 Esc —— 比如用户正在 textarea 里写长文,按 Esc 不该关闭对话框,更不该清空输入内容。
实操关键是动态控制监听器的启用/禁用,而不是靠条件判断“绕过”逻辑。
- 对话框显示时调用
document.addEventListener('keydown', escHandler) - 对话框隐藏时立即调用
document.removeEventListener('keydown', escHandler) - 不要在 handler 里写
if (!dialogOpen) return—— 这仍会执行函数体、增加不必要的判断开销,且容易漏掉移除时机 - 若用 Vue/React 等框架,确保监听器注册和销毁与组件生命周期严格对齐(比如
onMounted/onUnmounted或useEffect清理函数)
焦点管理不补上,Esc 关闭后用户就“卡住”了
Esc 关闭对话框后,焦点不会自动回到触发它的按钮或上一个可聚焦元素。用户按 Tab 会跳进页面底部甚至地址栏,这是无障碍严重缺陷。
必须手动还原焦点,且时机很关键:不能在 removeEventListener 后立刻 focus(),得等 DOM 更新完成。
- 记录打开前的
document.activeElement,存为变量(如lastFocusedEl) - 关闭逻辑中,先移除监听器,再用
setTimeout(() => lastFocusedEl?.focus(), 0)延迟聚焦 - 如果对话框是模态的(带遮罩层),遮罩层本身需设
tabindex="-1"并focus(),否则焦点可能落到不可见区域 - 别依赖
autofocus属性——它只在元素插入 DOM 时生效,关闭后无法回退
Mac 用户按 Cmd+Period 也能关?别硬编码 Esc
macOS 系统级关闭模态窗口的快捷键是 Cmd + .(句点),不少用户习惯这么操作。纯监听 Escape 会漏掉这部分人。
这不是“额外功能”,而是跨平台基础一致性要求。但注意:不能简单加个 e.metaKey && e.key === '.' 就完事。
- 仅当对话框处于激活态(即遮罩层可见、且无其他更高层浮层)时,才响应
metaKey + . - 必须同步执行焦点还原逻辑,和 Esc 完全一致
- 避免和编辑器类插件冲突:检查
e.target是否为input、textarea或contenteditable元素,是则跳过 - 不用
e.code === 'Period'—— 某些非美式键盘(如德语)句点键的code是Slash,而e.key在 Cmd+. 时稳定返回'.'
真实项目里最常被忽略的,是焦点还原的异步时机和多层模态叠加时的 activeElement 记录粒度。一个页面开两个对话框再关闭,若只记一次上一焦点,第二次还原就会错乱。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











