首页 / Wails 入门教程 / 右键菜单与鼠标按钮

Wails 入门教程

右键菜单与鼠标按钮

本教程共 42 篇 · 第 22 篇 · 更新于 2026-08-03

Wails桌面开发鼠标按钮右键mousedown

22. 右键菜单与鼠标按钮

本节目标

  • 理解为什么 window.onclick 识别不了鼠标按键。
  • event.button 区分左键、右键、中键和侧键。
  • 在无边框窗口里给右键做出自己的菜单。

22-1 为什么 onclick 不够用

做桌面应用,经常要响应鼠标右键。

比如右键弹个菜单,或者中键做点特别的事。

很多人第一反应是用 window.onclick。结果一跑,右键、中键全都识别不出来。

原因在 Wails 的运行机制上。框架会拦截鼠标点击,用来判断要不要缩放窗口、要不要拖动无边框窗口。

这一层拦截之后,普通 onclick 拿到的按键信息就不可靠了。

这不是框架的 bug,而是桌面应用的常态。无边框窗口靠拖拽 CSS 属性移动,缩放靠边缘命中,这些都需要框架先截获鼠标事件再决定如何分发。代价就是原生 click 的语义被拆开,你只能从更底层的 mousedown 拿原始按键。理解了这点,后面所有鼠标交互都顺着 mousedown 走就不会走偏。

Note

真正可靠的入口是 mousedown 事件,而不是 clickmousedown 在按钮按下的瞬间就触发,能原样拿到按键编号。

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 鼠标坐标与屏幕关系

右键菜单经常需要知道鼠标在哪儿。事件对象自带 clientXclientY,那是相对浏览器视口的坐标。

function onContentMouseDown(e: MouseEvent) {
    if (e.button === 2) {
        console.log(e.clientX, e.clientY); // 视口坐标
    }
}

用这两个值直接定位你的 HTML 菜单(记得 position: fixed),菜单就会贴在鼠标旁边。

Tip

如果菜单要传给 Go 侧做原生定位,注意 clientX/clientY 是前端视口坐标,不是系统全局坐标。需要全局坐标时,加上 window.screenXwindow.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); // 右键取色
    }
}

中键默认有”自动滚动”行为,点一下进入滚动模式。在 mousedownpreventDefault 可以关掉它,避免干扰你的逻辑。

Warning

不是所有鼠标都有侧键,也不是所有用户会用。侧键功能属于锦上添花,核心交互别只放在侧键上,主路径还是用左键。

Tip

画布坐标优先用 offsetX/offsetY 而不是 clientX/clientY,省去自己减元素偏移的麻烦,也避免了滚动导致的误差。

22-8 排错清单

鼠标按钮相关的坑。

第一,右键菜单不弹。先确认用 mousedown 而不是 onclick,再用 e.preventDefault() 关掉原生 contextmenu

第二,按钮值对不上。比较用 === 2,类型是 number 不是 string。

第三,中键触发了自动滚动。在 mousedownpreventDefault 关掉。

第四,无边框窗口里右键被拖拽逻辑吃掉。把右键逻辑放到非 drag 区域,或标题栏只处理左键。

Tip

想快速验证 event.button 的值,在 mousedownconsole.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,否则你的菜单和系统菜单会叠在一起,体验很差。