项目介绍
这是一个基于Electron开发的下拉菜单组件示例,展示了如何在Electron应用中实现功能丰富、交互友好的下拉菜单。该组件适用于桌面应用的导航菜单、上下文菜单、设置选项等多种场景,为用户提供便捷的选项选择方式。

功能特点
- 多种菜单变体:支持默认菜单、带图标菜单、多选菜单、级联菜单等多种类型
- 丰富的交互效果:包含悬停高亮、点击选中、展开/折叠动画等
- 键盘导航支持:支持通过方向键导航菜单选项,回车确认选择
- 自定义样式:可配置菜单宽度、背景色、字体颜色、边框等样式
- 事件回调:提供选择、取消、打开、关闭等多种事件回调
- 级联子菜单:支持多级子菜单的嵌套展示和交互
- 响应式设计:适配不同屏幕尺寸和窗口大小
技术实现
主进程实现 (main.js)
- 使用Electron的
app和BrowserWindow模块创建应用窗口 - 配置窗口大小为800x600像素
- 设置安全策略:禁用Node.js集成,启用上下文隔离
- 预加载脚本配置,确保渲染进程安全访问Electron API
预加载脚本 (preload.js)
- 使用
contextBridge模块安全地暴露Electron API到渲染进程 - 目前组件主要在渲染进程实现,暴露空的electronAPI对象
渲染进程实现 (renderer.js)
- 实现DropdownManager类,统一管理所有下拉菜单
- 支持动态创建和销毁菜单
- 实现菜单的显示、隐藏、展开、折叠逻辑
- 事件处理:点击触发、悬停效果、键盘导航、选项选择等
- 级联菜单处理:子菜单的展开和关闭逻辑
- 多选菜单支持:选项的选中状态管理
界面设计 (index.html, style.css)
- 下拉菜单基础样式:边框、阴影、圆角等
- 菜单选项样式:正常状态、悬停状态、选中状态
- 图标集成:支持为菜单项添加图标
- 动画效果:展开/折叠的平滑过渡动画
- 级联菜单指示器:右侧箭头指示子菜单的存在
- 响应式布局:在不同屏幕尺寸下的自适应表现
代码结构
84-dropdown-menu/
├── main.js # Electron主进程
├── preload.js # 预加载脚本
├── index.html # 主界面
├── style.css # 样式文件
├── renderer.js # 渲染进程脚本
└── package.json # 项目配置
核心代码说明
DropdownManager类
javascript
class DropdownManager {
constructor() {
this.activeDropdowns = [];
this.initEvents();
}
// 初始化全局事件
initEvents() {
// 点击文档其他地方关闭下拉菜单
document.addEventListener('click', (e) => {
this.activeDropdowns.forEach(dropdown => {
const trigger = document.querySelector(`[data-dropdown="${dropdown.id}"]`);
if (trigger && !trigger.contains(e.target) && !dropdown.contains(e.target)) {
this.close(dropdown);
}
});
});
// 键盘导航
document.addEventListener('keydown', (e) => {
if (this.activeDropdowns.length === 0) return;
const activeDropdown = this.activeDropdowns[this.activeDropdowns.length - 1];
const items = activeDropdown.querySelectorAll('.dropdown-item:not(.dropdown-item-disabled)');
const activeItem = activeDropdown.querySelector('.dropdown-item-active');
const activeIndex = Array.from(items).indexOf(activeItem);
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
const nextIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0;
this.activateItem(items[nextIndex], activeDropdown);
break;
case 'ArrowUp':
e.preventDefault();
const prevIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
this.activateItem(items[prevIndex], activeDropdown);
break;
case 'Enter':
if (activeItem) {
activeItem.click();
}
break;
case 'Escape':
this.close(activeDropdown);
break;
}
});
}
// 激活菜单项
activateItem(item, dropdown) {
// 移除之前激活的项
dropdown.querySelectorAll('.dropdown-item-active').forEach(el => {
el.classList.remove('dropdown-item-active');
});
// 激活当前项
item.classList.add('dropdown-item-active');
// 滚动到可视区域
item.scrollIntoView({ block: 'nearest' });
}
// 切换下拉菜单
toggle(dropdownId) {
const dropdown = document.getElementById(dropdownId);
if (!dropdown) return;
// 检查是否已经激活
const isActive = this.activeDropdowns.includes(dropdown);
if (isActive) {
this.close(dropdown);
} else {
this.open(dropdown);
}
}
// 打开下拉菜单
open(dropdown) {
// 关闭其他所有下拉菜单
this.activeDropdowns.forEach(d => this.close(d));
// 添加到活动下拉菜单列表
this.activeDropdowns.push(dropdown);
// 显示下拉菜单
dropdown.style.display = 'block';
// 触发动画
setTimeout(() => {
dropdown.classList.add('dropdown-open');
}, 10);
}
// 关闭下拉菜单
close(dropdown) {
if (!dropdown) return;
// 移除动画
dropdown.classList.remove('dropdown-open');
// 隐藏下拉菜单
setTimeout(() => {
dropdown.style.display = 'none';
// 从活动下拉菜单列表中移除
const index = this.activeDropdowns.indexOf(dropdown);
if (index > -1) {
this.activeDropdowns.splice(index, 1);
}
// 清除激活状态
dropdown.querySelectorAll('.dropdown-item-active').forEach(el => {
el.classList.remove('dropdown-item-active');
});
}, 300);
}
}
创建下拉菜单
javascript
function createDropdown(options) {
const defaultOptions = {
id: `dropdown-${Date.now()}`,
triggerId: null,
items: [],
position: 'bottom', // top, bottom, left, right
width: 'auto',
multiSelect: false
};
const settings = { ...defaultOptions, ...options };
// 创建下拉菜单容器
const dropdown = document.createElement('div');
dropdown.id = settings.id;
dropdown.className = `dropdown dropdown-${settings.position}`;
dropdown.style.width = settings.width;
dropdown.style.display = 'none';
// 生成菜单项
const menu = document.createElement('ul');
menu.className = 'dropdown-menu';
settings.items.forEach(item => {
const li = document.createElement('li');
li.className = 'dropdown-item';
// 添加禁用状态
if (item.disabled) {
li.classList.add('dropdown-item-disabled');
}
// 添加图标
if (item.icon) {
const icon = document.createElement('span');
icon.className = 'dropdown-item-icon';
icon.innerHTML = item.icon;
li.appendChild(icon);
}
// 添加文本
const text = document.createElement('span');
text.className = 'dropdown-item-text';
text.textContent = item.text;
li.appendChild(text);
// 添加多选框
if (settings.multiSelect) {
const checkbox = document.createElement('span');
checkbox.className = 'dropdown-item-checkbox';
if (item.selected) {
checkbox.classList.add('dropdown-item-checked');
}
li.appendChild(checkbox);
}
// 添加子菜单指示器
if (item.submenu && item.submenu.length > 0) {
const arrow = document.createElement('span');
arrow.className = 'dropdown-item-arrow';
arrow.innerHTML = '>>';
li.appendChild(arrow);
// 创建子菜单
const submenu = createSubmenu(item.submenu);
li.appendChild(submenu);
}
// 添加点击事件
if (!item.disabled) {
li.addEventListener('click', () => {
// 处理多选
if (settings.multiSelect) {
const checkbox = li.querySelector('.dropdown-item-checkbox');
checkbox.classList.toggle('dropdown-item-checked');
}
// 执行回调
if (item.callback) {
item.callback();
}
// 非多选菜单点击后关闭
if (!settings.multiSelect && !item.submenu) {
dropdownManager.close(dropdown);
}
});
// 添加悬停事件
li.addEventListener('mouseenter', () => {
// 关闭其他子菜单
li.querySelectorAll('.dropdown-submenu').forEach(sub => {
dropdownManager.close(sub);
});
// 打开当前子菜单
if (item.submenu && item.submenu.length > 0) {
const submenu = li.querySelector('.dropdown-submenu');
dropdownManager.open(submenu);
}
});
}
menu.appendChild(li);
});
dropdown.appendChild(menu);
// 添加到文档
document.body.appendChild(dropdown);
// 绑定触发器
if (settings.triggerId) {
const trigger = document.getElementById(settings.triggerId);
if (trigger) {
trigger.dataset.dropdown = settings.id;
trigger.addEventListener('click', (e) => {
e.stopPropagation();
dropdownManager.toggle(settings.id);
});
}
}
return dropdown;
}
// 创建子菜单
function createSubmenu(items) {
const submenu = document.createElement('div');
submenu.className = 'dropdown dropdown-submenu';
submenu.style.display = 'none';
const menu = document.createElement('ul');
menu.className = 'dropdown-menu';
items.forEach(item => {
// 与主菜单项创建逻辑类似
const li = document.createElement('li');
li.className = 'dropdown-item';
// 这里省略部分代码,与主菜单类似
// ...
});
submenu.appendChild(menu);
return submenu;
}
如何运行
- 安装依赖
bash
npm install
- 启动应用
bash
npm start
扩展建议
- 添加右键菜单功能
- 实现菜单搜索功能
- 支持自定义快捷键
- 添加动画变体选择
- 实现深色/浅色主题切换
注意事项
- 菜单层级不宜过深,影响用户体验
- 确保菜单项有足够的点击区域
- 菜单项文本不宜过长,需要考虑换行或截断
- 在实际应用中,考虑性能优化,避免过多DOM操作
鸿蒙PC适配改造指南
1. 环境准备
-
系统要求:Windows 10/11、8GB RAM以上、20GB可用空间
-
工具安装 :
DevEco Studio 5.0+(安装鸿蒙SDK API 20+)
-
Node.js 18.x+
2. 获取Electron鸿蒙编译产物
-
下载Electron 34+版本的Release包(.zip格式)
-
解压到项目目录,确认
electron/libs/arm64-v8a/下包含核心.so库
3. 部署应用代码
将Electron应用代码按以下目录结构放置:

plaintext
web_engine/src/main/resources/resfile/resources/app/
├── main.js
├── package.json
└── src/
├── index.html
├── preload.js
├── renderer.js
└── style.css
4. 配置与运行
-
打开项目:在DevEco Studio中打开ohos_hap目录
-
配置签名 :
进入File → Project Structure → Signing Configs
-
自动生成调试签名或导入已有签名
-
连接设备 :
启用鸿蒙设备开发者模式和USB调试
-
通过USB Type-C连接电脑
-
编译运行:点击Run按钮或按Shift+F10
5. 验证检查项
-
✅ 应用窗口正常显示
-
✅ 窗口大小可调整,响应式布局生效
-
✅ 控制台无"SysCap不匹配"或"找不到.so文件"错误
-
✅ 动画效果正常播放
跨平台兼容性
| 平台 | 适配策略 | 特殊处理 |
|---|---|---|
| Windows | 标准Electron运行 | 无特殊配置 |
| macOS | 标准Electron运行 | 保留dock图标激活逻辑 |
| Linux | 标准Electron运行 | 确保系统依赖库完整 |
| 鸿蒙PC | 通过Electron鸿蒙适配层 | 禁用硬件加速,使用特定目录结构 |
鸿蒙开发调试技巧
1. 日志查看
在DevEco Studio的Log面板中过滤"Electron"关键词,查看应用运行日志和错误信息。
2. 常见问题解决
-
"SysCap不匹配"错误:检查module.json5中的reqSysCapabilities,只保留必要系统能力
-
"找不到.so文件"错误:确认arm64-v8a目录下四个核心库文件完整
-
窗口不显示:在main.js中添加app.disableHardwareAcceleration()
-
动画卡顿:简化CSS动画效果,减少重绘频率