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。你也可以为菜单项设置 enabled 和 visible 属性来控制状态。
点击托盘的交互
不同平台的用户对托盘点击行为有不同的期待。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 技术构建的应用。