人人都会AI编程

Tray 托盘图标与右键菜单

更新时间:2026-07-11

Tray(系统托盘)是桌面应用常驻后台时与用户保持轻量交互的重要入口。即使主窗口被关闭,一个托盘图标仍能让你随时弹出一条提醒、切换设置或快速恢复窗口。Electron 对托盘的支持非常直接,几行代码就能挂上图标并绑定菜单。

创建基本托盘

托盘由主进程管理,通常在 app.whenReady() 后创建。你需要准备一张图标(建议 16×16 或 24×24 的 PNG,macOS 上推荐使用模板图片以获得自动明暗适配)。

const { app, Tray, Menu, nativeImage } = require('electron');
const path = require('path');

let tray;

app.whenReady().then(() => {
  // 从文件创建图标,nativeImage 可以调整尺寸避免模糊
  const icon = nativeImage.createFromPath(
    path.join(__dirname, 'assets', 'tray-icon.png')
  ).resize({ width: 16, height: 16 });

  tray = new Tray(icon);

  // 设置鼠标悬停提示
  tray.setToolTip('我的应用');
});

如果你希望在 macOS 上实现“按下状态”的高亮效果,可以改用 nativeImage.createFromPath 加载一张 PDF 或带透明通道的 PNG,并调用 tray.setPressedImage(iconPressed)

绑定右键菜单

托盘的灵魂在于菜单。Electron 的 Menu 模块可以让你像搭积木一样组合各种操作:

const contextMenu = Menu.buildFromTemplate([
  {
    label: '显示主窗口',
    click: () => {
      // 在这个回调里获取当前窗口或创建新窗口
      const win = getMainWindow(); // 你自己实现的函数
      if (win) {
        win.show();
        win.focus();
      }
    },
  },
  {
    label: '暂停同步',
    type: 'checkbox',
    checked: true, // 可以动态改变
    click: (menuItem) => {
      // menuItem.checked 会由 Electron 自动切换
      toggleSync(menuItem.checked);
    },
  },
  { type: 'separator' },
  {
    label: '退出',
    click: () => {
      app.quit();
    },
  },
]);

// 把菜单挂到托盘上
tray.setContextMenu(contextMenu);

菜单项支持常见的类型:普通 normal、分隔线 separator、复选框 checkbox、单选按钮 radio 以及子菜单 submenu。你也可以为菜单项设置 enabledvisible 属性来控制状态。

点击托盘的交互

不同平台的用户对托盘点击行为有不同的期待。Windows 习惯左键点击弹出菜单,macOS 则支持左键点击直接触发某个默认操作。我们可以通过监听事件来适配:

// Windows 上左键点击一般弹出菜单,右键点击也弹出菜单,只需绑定一次 contextMenu
// macOS 上可以监听 click 事件做自定义行为
tray.on('click', (event, bounds) => {
  // bounds 包含托盘图标的位置,可用于定位自定义窗口
  const win = getMainWindow();
  if (win) {
    win.show();
    win.focus();
  }
});

// 双击可以保留空函数或执行其他逻辑
tray.on('double-click', () => {});

使用 tray.popUpContextMenu() 可以在鼠标位置手动弹出菜单,通常结合 right-click 事件:

tray.on('right-click', () => {
  tray.popUpContextMenu(contextMenu);
});

动态更新与气泡通知

托盘图标和菜单并不是一成不变的。你可以根据应用状态实时切换图标(比如消息通知的未读标记)或更新菜单项的标签。

function setUnreadState(hasUnread) {
  const newIcon = nativeImage.createFromPath(
    path.join(__dirname, 'assets', hasUnread ? 'tray-unread.png' : 'tray-normal.png')
  ).resize({ width: 16, height: 16 });
  tray.setImage(newIcon);
}

如果需要弹出系统通知(Windows 的气泡提示或 macOS 的通知横幅),调用:

tray.displayBalloon({
  title: '新消息',
  content: '你收到了一条新的评论',
  icon: nativeImage.createFromPath(path.join(__dirname, 'assets', 'info.png')),
});

注意:displayBalloon 在 Windows 上表现为气泡提示,macOS 上则降级为系统通知(需提前请求通知权限),Linux 部分桌面环境可能不支持。

托盘与窗口生命周期的合理配合

在实际应用中,用户关闭窗口时往往希望应用隐藏到托盘而非彻底退出。你可以在窗口的 close 事件里拦截默认行为:

win.on('close', (event) => {
  if (!isQuitting) {
    event.preventDefault();
    win.hide(); // 隐藏窗口
  }
});

// 真正退出时设置标志
function quitApp() {
  isQuitting = true;
  app.quit();
}

通过托盘菜单的“退出”按钮调用 quitApp(),从而优雅地终止程序。

注意事项

  • 图标资源:macOS 上强烈建议使用 16×16 或 22×22 的 Template 图片(文件名以 Template@2x.png 结尾),系统会自动根据深色/浅色模式对图片着色,避免白色图标在浅色菜单栏上“消失”。
  • 跨平台差异:Linux 并非所有桌面环境都有托盘区,建议在使用前判断 tray.isDestroyed() 或捕获异常。
  • 内存泄漏:如果托盘对象不再需要,调用 tray.destroy() 释放资源,避免退出时残留图标。

Tray 的集成让 Electron 应用在后台状态下依然保持“存活感”。对于聊天工具、下载管理器、便签类应用来说,这是一个低成本但极高感知度的功能点。配合灵活的菜单和事件处理,你完全可以让用户几乎忘记这是个 Web 技术构建的应用。