右键菜单与鼠标按钮
本教程共 42 篇 · 第 22 篇 · 更新于 2026-08-03
22. 右键菜单与鼠标按钮
本节目标
- 理解为什么
window.onclick识别不了鼠标按键。 - 用
event.button区分左键、右键、中键和侧键。 - 在无边框窗口里给右键做出自己的菜单。
22-1 为什么 onclick 不够用
做桌面应用,经常要响应鼠标右键。
比如右键弹个菜单,或者中键做点特别的事。
很多人第一反应是用 window.onclick。结果一跑,右键、中键全都识别不出来。
原因在 Wails 的运行机制上。框架会拦截鼠标点击,用来判断要不要缩放窗口、要不要拖动无边框窗口。
这一层拦截之后,普通 onclick 拿到的按键信息就不可靠了。
这不是框架的 bug,而是桌面应用的常态。无边框窗口靠拖拽 CSS 属性移动,缩放靠边缘命中,这些都需要框架先截获鼠标事件再决定如何分发。代价就是原生 click 的语义被拆开,你只能从更底层的 mousedown 拿原始按键。理解了这点,后面所有鼠标交互都顺着 mousedown 走就不会走偏。
Note真正可靠的入口是
mousedown事件,而不是click。mousedown在按钮按下的瞬间就触发,能原样拿到按键编号。
22-2 用 event.button 区分按键
MouseEvent 上有个 button 属性,用数字表示按的是哪个键:
| 数字 | 含义 |
|---|---|
| 0 | 鼠标左键 |
| 1 | 鼠标中键 |
| 2 | 鼠标右键 |
| 3 | 后退键 |
| 4 | 前进键 |
监听 mousedown,按编号分流即可:
window.addEventListener("mousedown", handleMouseButtonDown);
function handleMouseButtonDown(event: MouseEvent) {
if (event.button === 0) {
// 左键
} else if (event.button === 1) {
// 中键
} else if (event.button === 2) {
// 右键
} else if (event.button === 3) {
// 后退键
} else if (event.button === 4) {
// 前进键
} else {
// 其它按键
}
}
Tip想屏蔽浏览器默认右键菜单,记得在容器上加
oncontextmenu={e => e.preventDefault()}。不然你自己的菜单和系统的会一起弹出来。
22-3 给右键做一个菜单
最常见的需求:右键弹个小菜单。
思路是,右键时记下鼠标位置,再显示一个绝对定位的 div。点别处就关掉它。
import { useState } from "react";
function Editor() {
const [menu, setMenu] = useState<{x: number; y: number} | null>(null);
function onMouseDown(e: MouseEvent) {
if (e.button === 2) {
setMenu({ x: e.clientX, y: e.clientY });
} else {
setMenu(null);
}
}
return (
<div onMouseDown={onMouseDown} onContextMenu={(e) => e.preventDefault()}>
{menu && (
<ul style={{ position: "fixed", left: menu.x, top: menu.y }}>
<li onClick={() => setMenu(null)}>复制</li>
<li onClick={() => setMenu(null)}>粘贴</li>
</ul>
)}
</div>
);
}
这样右键就在鼠标位置弹出菜单,点其它地方自动消失。
Note这个菜单是纯前端 React 组件,和 Wails 的应用菜单(第 23 章)不是一回事。应用菜单是系统原生的,这个是你自己画的 HTML。
22-4 把按键事件交给 Go
如果你的右键逻辑要动后端,比如删除一个文件,就把事件上报给 Go。
先在前端调用绑定方法:
function onMouseDown(e: MouseEvent) {
if (e.button === 2) {
window.go.main.App.OnRightClick(e.clientX, e.clientY);
}
}
Go 侧接收并处理:
func (a *App) OnRightClick(x, y int) {
// 在这里做删除、弹出原生对话框等后端操作
rt.MessageDialog(a.ctx, rt.MessageDialogOptions{
Type: rt.QuestionDialog,
Title: "确认",
Message: "要删除这条记录吗?",
})
}
Tip复杂的交互放 Go 侧更合适,比如读写文件、调用系统能力。纯 UI 显隐就留在 React 里,职责分清,代码也好维护。
22-5 无边框下屏蔽右键与拖拽冲突
无边框窗口里,右键既要弹菜单,又不能误触发拖拽。因为 drag 区域是 CSS 控制的,右键按下也会被当成拖拽起点。
解决办法是让右键逻辑和拖拽区域分开。常见做法是:标题栏 drag 区只处理拖拽,右键菜单只绑定到内容区。
<div onMouseDown={onTitleMouseDown} style={{ "--wails-draggable": "drag" } as React.CSSProperties}>
{/* 标题栏,只处理拖拽 */}
</div>
<div onMouseDown={onContentMouseDown} onContextMenu={(e) => e.preventDefault()}>
{/* 内容区,处理右键菜单 */}
</div>
event.button 的判断放在各自的处理函数里。标题栏只对左键(0)做拖拽相关处理,内容区对右键(2)弹菜单。职责分清,互不干扰。
Warning别在 drag 区域里既做拖拽又监听右键菜单。Wails 拦截鼠标时,右键可能被拖拽逻辑先吃掉,你的菜单弹不出来。
22-6 鼠标坐标与屏幕关系
右键菜单经常需要知道鼠标在哪儿。事件对象自带 clientX 和 clientY,那是相对浏览器视口的坐标。
function onContentMouseDown(e: MouseEvent) {
if (e.button === 2) {
console.log(e.clientX, e.clientY); // 视口坐标
}
}
用这两个值直接定位你的 HTML 菜单(记得 position: fixed),菜单就会贴在鼠标旁边。
Tip如果菜单要传给 Go 侧做原生定位,注意
clientX/clientY是前端视口坐标,不是系统全局坐标。需要全局坐标时,加上window.screenX和window.screenY做换算。
22-7 中键、侧键与画布坐标
除了左键右键,鼠标还有中键(1)和侧键(3 后退、4 前进)。
中键在桌面应用里常用来”在新标签打开”或”快速关闭标签”。侧键多用于浏览器前进后退,你的应用里可以映射成上一步、下一步。
function onMouseDown(e: MouseEvent) {
if (e.button === 1) {
e.preventDefault(); // 阻止中键的自动滚动模式
window.go.main.App.OpenInNewTab();
} else if (e.button === 3) {
window.go.main.App.GoBack();
}
}
做画板类应用时,坐标的取法更讲究。事件里有 clientX/clientY(相对视口)和 offsetX/offsetY(相对当前元素)。画布上落笔,用 offsetX/offsetY 最准,因为它已经扣掉了元素自身的偏移。
function onCanvasDown(e: MouseEvent) {
if (e.button === 0) {
drawAt(e.offsetX, e.offsetY);
} else if (e.button === 2) {
pickColorAt(e.offsetX, e.offsetY); // 右键取色
}
}
中键默认有”自动滚动”行为,点一下进入滚动模式。在 mousedown 里 preventDefault 可以关掉它,避免干扰你的逻辑。
Warning不是所有鼠标都有侧键,也不是所有用户会用。侧键功能属于锦上添花,核心交互别只放在侧键上,主路径还是用左键。
Tip画布坐标优先用
offsetX/offsetY而不是clientX/clientY,省去自己减元素偏移的麻烦,也避免了滚动导致的误差。
22-8 排错清单
鼠标按钮相关的坑。
第一,右键菜单不弹。先确认用 mousedown 而不是 onclick,再用 e.preventDefault() 关掉原生 contextmenu。
第二,按钮值对不上。比较用 === 2,类型是 number 不是 string。
第三,中键触发了自动滚动。在 mousedown 里 preventDefault 关掉。
第四,无边框窗口里右键被拖拽逻辑吃掉。把右键逻辑放到非 drag 区域,或标题栏只处理左键。
Tip想快速验证
event.button的值,在mousedown里console.log(e.button),拿不同键点几下,对照 0/1/2/3/4 确认你的分支走对了。另外,触屏设备没有鼠标按钮概念,touch 事件里button通常是 0,做移动端兼容时别把触屏当成左键去特殊处理。
常见误区
Warning别用
onclick判断按键。click事件在无边框窗口下经常拿不到正确的button值,一定要用mousedown。
Note
event.button是数字不是字符串。比较时用=== 2,不要写'2',类型对不上会永远进 else 分支。
Tip右键菜单记得
preventDefault掉原生contextmenu,否则你的菜单和系统菜单会叠在一起,体验很差。