自定义右键菜单需显式调用event.preventdefault()阻止原生菜单,菜单dom应挂载到document.body并用position:fixed定位,支持键盘焦点与边界检测,移动端需降级为长按触发。

oncontextmenu 事件默认行为必须显式阻止
直接绑定 oncontextmenu 不会自动隐藏原生右键菜单,必须在事件处理函数里调用 event.preventDefault(),否则自定义菜单弹出的同时原生菜单也会出现——这是最常被忽略的一步。
常见错误写法:oncontextmenu="showMenu()" 且 showMenu 函数里没写 event.preventDefault(),结果双菜单叠加,体验混乱。
- 推荐用
addEventListener('contextmenu', handler, true)绑定,便于统一管理 - handler 必须接收 event 参数,并第一时间调用
event.preventDefault() - 如果用内联写法,需传入
event:oncontextmenu="showMenu(event)"
自定义菜单 DOM 要脱离 document.body 直接挂载
右键菜单需要随鼠标位置动态定位,且不能被父容器的 overflow: hidden 或 transform 截断。最稳妥方式是把菜单元素直接 append 到 document.body。
容易踩的坑:把菜单放在某个 div 内部,结果右键位置靠近父容器边缘时菜单被裁剪,或者因父级 position: relative 导致定位偏移。
- 菜单元素建议用
position: fixed,配合event.clientX/event.clientY定位 - 注意边界检测:若靠近屏幕右/下边缘,需向左或向上偏移,避免菜单溢出
- 菜单显示后,记得监听一次
click或blur隐藏它,否则点其他地方不消失
右键菜单点击后需手动触发 focus 和键盘支持
原生右键菜单支持键盘操作(上下键切换、回车确认),但自定义菜单默认没有焦点管理,用户按键盘毫无反应——这会让无障碍和习惯键盘操作的人直接放弃使用。
关键点不是“做多复杂”,而是至少让第一个菜单项可 focus,且支持 Enter 和 Escape。
- 菜单项用
<button></button>或带tabindex="0"的<div> <li>显示菜单后,用 <code>menuItems[0].focus()自动聚焦首个选项 - 监听
keydown:捕获Escape隐藏菜单,ArrowDown/ArrowUp切换焦点 - 不要依赖
oncontextmenu捕获右键点击本身来关闭菜单——右键不会触发 click - 检测
'ontouchstart' in window,移动端改用长按(touchstart + setTimeout)模拟右键 - 给目标元素加
onlongpresspolyfill,或用pointerdown+ 时间阈值判断 - 服务端或构建时通过 UA 判断,对移动端返回简化版操作浮层,而非完整右键菜单
移动端 Safari 和部分 Chrome 版本不触发 contextmenu
iOS Safari 默认禁用 contextmenu 事件,即使加了 preventDefault 也收不到;Android Chrome 从 v106 开始对非桌面设备也限制该事件——这意味着纯靠 oncontextmenu 的方案在移动端基本失效。
真实项目中若需跨端一致行为,得降级处理:
别指望 CSS user-select: none 或 -webkit-touch-callout: none 能唤醒 contextmenu——它们只影响系统菜单样式,不触发事件。











