发布于 2026-01-06 1 阅读
0

在 Electron 中创建自定义菜单栏

在 Electron 中创建自定义菜单栏

(原文发表于我的博客

想让你的 Electron 应用的菜单栏看起来更酷炫吗?让我们来看看如何创建一个类似 Slack 菜单栏的自定义菜单栏。

先决条件

ElectronJS 基础知识。请查看本教程开始学习。

资源

完整的代码可在https://github.com/saisandeepvaddi/electron-custom-menu-bar获取。

我们将建造什么

这就是我们完工后的样子。

点击菜单前的结果图像

菜单打开时的结果图像

鼠标悬停在图像上即可查看结果

设置 Electron 项目

根据 Electron 官方快速入门 GitHub 仓库搭建一个最小的 Electron 应用。



# Clone the Quick Start repository
$ git clone https://github.com/electron/electron-quick-start

# Go into the repository
$ cd electron-quick-start

# Install the dependencies and run
$ npm install && npm start



Enter fullscreen mode Exit fullscreen mode

主进程代码

首次运行时,npm start您会看到一个带有默认菜单栏的窗口。要将其替换为我们自己的菜单栏,我们需要做两件事。在main.js我们拥有的文件中,

  1. frame: falseoptions对象中设置new BrowserWindow({frame: false, ...other-options})。这将创建一个没有工具栏、边框等的窗口。有关更多详细信息,请查看无边框窗口。
  2. 注册一个事件监听器,ipcMain当鼠标点击汉堡图标时,该监听器会接收鼠标点击位置。


// main.js

  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, "preload.js")
      // (NOT RECOMMENDED)
      // If true, we can skip attaching functions from ./menu-functions.js to window object in preload.js.
      // And, instead, we can use electron APIs directly in renderer.js
      // From Electron v5, nodeIntegration is set to false by default. And it is recommended to use preload.js to get access to only required Node.js apis.
      // nodeIntegration: true
    },
    frame: false //Remove frame to hide default menu
  });
  // ...other stuff
}

// Register an event listener. When ipcRenderer sends mouse click co-ordinates, show menu at that position.
ipcMain.on(`display-app-menu`, function(e, args) {
  if (isWindows && mainWindow) {
    menu.popup({
      window: mainWindow,
      x: args.x,
      y: args.y
    });
  }
});

// ... other stuff.


Enter fullscreen mode Exit fullscreen mode

创建一个名为 `<filename>` 的文件menu-functions.js,并在其中定义以下函数。这里的所有函数都接收 ElectronBrowserWindow对象(mainWindow在本应用中),并执行最小化、最大化、关闭和打开菜单等操作,这些操作需要从我们自定义的菜单栏触发。



// menu-functions.js
const { remote, ipcRenderer } = require("electron");

function getCurrentWindow() {
  return remote.getCurrentWindow();
}

function openMenu(x, y) {
  ipcRenderer.send(`display-app-menu`, { x, y });
}

function minimizeWindow(browserWindow = getCurrentWindow()) {
  if (browserWindow.minimizable) {
    // browserWindow.isMinimizable() for old electron versions
    browserWindow.minimize();
  }
}

function maximizeWindow(browserWindow = getCurrentWindow()) {
  if (browserWindow.maximizable) {
    // browserWindow.isMaximizable() for old electron versions
    browserWindow.maximize();
  }
}

function unmaximizeWindow(browserWindow = getCurrentWindow()) {
  browserWindow.unmaximize();
}

function maxUnmaxWindow(browserWindow = getCurrentWindow()) {
  if (browserWindow.isMaximized()) {
    browserWindow.unmaximize();
  } else {
    browserWindow.maximize();
  }
}

function closeWindow(browserWindow = getCurrentWindow()) {
  browserWindow.close();
}

function isWindowMaximized(browserWindow = getCurrentWindow()) {
  return browserWindow.isMaximized();
}

module.exports = {
  getCurrentWindow,
  openMenu,
  minimizeWindow,
  maximizeWindow,
  unmaximizeWindow,
  maxUnmaxWindow,
  isWindowMaximized,
  closeWindow,
};


Enter fullscreen mode Exit fullscreen mode

我们需要将这些函数附加到window对象上,以便在渲染进程中使用。如果您使用的是旧版本的 Electron(<5.0.0)或者您nodeIntegration: trueBrowserWindow`s` 选项中进行了设置,则可以menu-functions.js在渲染进程中直接使用上述文件。出于安全考虑false,Electron 新版本默认已启用此功能



// preload.js
const { remote } = require("electron");
const {
  getCurrentWindow,
  openMenu,
  minimizeWindow,
  unmaximizeWindow,
  maxUnmaxWindow,
  isWindowMaximized,
  closeWindow,
} = require("./menu-functions");

window.addEventListener("DOMContentLoaded", () => {
  window.getCurrentWindow = getCurrentWindow;
  window.openMenu = openMenu;
  window.minimizeWindow = minimizeWindow;
  window.unmaximizeWindow = unmaximizeWindow;
  window.maxUnmaxWindow = maxUnmaxWindow;
  window.isWindowMaximized = isWindowMaximized;
  window.closeWindow = closeWindow;
});


Enter fullscreen mode Exit fullscreen mode

现在我们需要一个菜单​​。请在新文件中创建一个简单的菜单。您可以在官方文档menu.js中了解如何向菜单中添加自定义选项。Electron 提供了一些易于理解且包含示例的文档。



// menu.js

const { app, Menu } = require("electron");

const isMac = process.platform === "darwin";

const template = [
  {
    label: "File",
    submenu: [isMac ? { role: "close" } : { role: "quit" }],
  },
];

const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);

module.exports = {
  menu,
};


Enter fullscreen mode Exit fullscreen mode

主流程部分已经完成。现在,让我们来构建自定义菜单栏。如果您查看图片中的菜单,您会看到我们的菜单栏上有这些选项。

  1. 左侧有一个汉堡图标,点击即可打开菜单。
  2. 右侧有最小化按钮、最大化/取消最大化按钮和关闭按钮。

我使用了来自fontawesome.com的 Font Awesome JS 文件来添加图标。请将其添加到 HTML 的 `<head>`<head>标签中。Content-Security-Policy为了暂时让 Font Awesome JS 文件运行,我移除了 meta 标签。在​​生产环境中,请确保正确配置代码的运行权限。更多详情请查看内容安全策略 (CSP) 。



<!-- index.html -->
<!DOCTYPE html>
<html>
 <head>
   <meta charset="UTF-8" />
   <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->

   <title>My Awesome App</title>
   <link rel="stylesheet" href="style.css" />
   <script src="https://kit.fontawesome.com/1c9144b004.js" crossorigin="anonymous"></script>
   </head>
 </head>
 <body>
   <div id="menu-bar">
     <div class="left" role="menu">
       <button class="menubar-btn" id="menu-btn"><i class="fas fa-bars"></i></button>
       <h5>My Awesome App</h5>
     </div>
     <div class="right">
       <button class="menubar-btn" id="minimize-btn"><i class="fas fa-window-minimize"></i></button>
       <button class="menubar-btn" id="max-unmax-btn"><i class="far fa-square"></i></button>
       <button class="menubar-btn" id="close-btn"><i class="fas fa-times"></i></button>
     </div>
   </div>
   <div class="container">
     Hello there!
   </div>

   <!-- You can also require other files to run in this process -->
   <script src="./renderer.js"></script>
 </body>
</html>


Enter fullscreen mode Exit fullscreen mode


/* style.css */

body {
  padding: 0;
  margin: 0;
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  color: white;
}

#menu-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 30px;
  background: #34475a;
  -webkit-app-region: drag;
}

#menu-bar > div {
  height: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.menubar-btn {
  -webkit-app-region: no-drag;
}

.container {
  height: calc(100vh - 30px);
  background: #34475ab0;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 2em;
}

button {
  height: 100%;
  padding: 0 15px;
  border: none;
  background: transparent;
  outline: none;
}

button:hover {
  background: rgba(221, 221, 221, 0.2);
}

#close-btn:hover {
  background: rgb(255, 0, 0);
}

button i {
  color: white;
}


Enter fullscreen mode Exit fullscreen mode

现在你的窗户应该看起来像这样。太棒了!我们快完成了。

点击菜单前的结果图像

如果你猜对了,菜单栏里的按钮一个都没反应。因为我们没有给onclick它们添加事件监听器。现在就来添加。还记得我们window之前给对象添加了一些实用函数preload.js吗?我们会在按钮点击事件监听器里用到它们。



// renderer.js

window.addEventListener("DOMContentLoaded", () => {
  const menuButton = document.getElementById("menu-btn");
  const minimizeButton = document.getElementById("minimize-btn");
  const maxUnmaxButton = document.getElementById("max-unmax-btn");
  const closeButton = document.getElementById("close-btn");

  menuButton.addEventListener("click", e => {
    // Opens menu at (x,y) coordinates of mouse click on the hamburger icon.
    window.openMenu(e.x, e.y);
  });

  minimizeButton.addEventListener("click", e => {
    window.minimizeWindow();
  });

  maxUnmaxButton.addEventListener("click", e => {
    const icon = maxUnmaxButton.querySelector("i.far");

    window.maxUnmaxWindow();

    // Change the middle maximize-unmaximize icons.
    if (window.isWindowMaximized()) {
      icon.classList.remove("fa-square");
      icon.classList.add("fa-clone");
    } else {
      icon.classList.add("fa-square");
      icon.classList.remove("fa-clone");
    }
  });

  closeButton.addEventListener("click", e => {
    window.closeWindow();
  });
});


Enter fullscreen mode Exit fullscreen mode

就这样。重启应用后npm run start,新的菜单栏按钮应该就能正常工作了。

注意:为了简洁起见,以上脚本中省略了部分代码。您可以在https://github.com/saisandeepvaddi/electron-custom-menu-bar获取完整代码。

如果你想查看一个功能更丰富的 Electron 应用,可以看看https://github.com/saisandeepvaddi/ten-hands这个应用,它使用了类似风格的菜单栏(不过目前自定义风格的菜单栏仅在 Windows 系统上可见),但它是用 React 和 TypeScript 构建的。我就是在使用了这个菜单栏之后才写这篇教程的。

谢谢。🙏

文章来源:https://dev.to/saisandeepvaddi/creating-a-custom-menu-bar-in-electron-1pi3