简介:面向希望将HTML网页快速封装为Windows桌面应用的开发者,这份资料完整呈现了基于Electron实现全屏展示、遮蔽任务栏、按Esc退出以及自定义exe图标与文件名的工程方案。内容覆盖BrowserWindow全屏配置、渲染进程与主进程的IPC通信、electron-packager/electron-builder打包参数调整等核心知识,适合具备一定HTML/CSS/JavaScript基础、需要桌面化交付或制作演示程序的读者。压缩包约95.61MB,共2000个文件,以js、ts、json、md等源码与配置文件为主,另含Electron相关二进制与依赖资源,目录结构完整可直接对照学习。已有1157人学习下载;资源里配有清晰的工程结构和关键代码示例,便于理解从网页到独立exe的整个封装流程,同时给出图标、文件名的定制思路与常见注意事项,是一份可动手验证的实践型参考资料。 最近做门店终端展示项目时,甲方提了个非常接地气的需求:给我一个exe,双击就能全屏展示网页,任务栏必须消失,按Esc才能退出,图标要用我们公司的,文件名也得改过来。这种需求在活动大屏、展厅终端、教学机、工业看板上太常见了,核心就是把一个HTML网页包装成看起来“很正规”的Windows桌面程序。
我选择Electron来干这个活。原因很简单:团队本身就是前端,用Electron打包exe是“前端顺手、零学习成本”的路径,不需要去啃C#或QT。网上关于Electron转exe的教程很多,但大多数只讲了“如何打开一个窗口”,真正涉及全屏遮任务栏、Esc强制退出、改图标换文件名这些细节的并不多,而这恰恰是实际交付时最容易翻车的点。这篇就结合我实际跑通的项目,把从零到交付的完整链路讲清楚。
1. 方案选型与整体思路拆解
1.1 为什么是Electron,而不是其他方案
能实现HTML转exe的方案其实不少,我大概对比过这么几个:
| 方案 | 原理 | 优势 | 劣势 |
|---|---|---|---|
| Electron | 内置Chromium内核,加载本地网页 | 前端零学习成本、生态成熟、兼容性最好 | 打包体积大(约150MB+) |
| Tauri | 调系统WebView(WebView2/WebKitGTK) | 体积小(几MB)、内存占用低 | 需要Rust环境、旧系统兼容性差 |
| Nw.js | 类似Electron,通过package.json配置 | 老牌方案、社区资料多 | 安全模型略老、API变化较大 |
| Python+PyQt | QWebEngine加载HTML | 适合Python团队 | 前端配合成本高、打包也大 |
如果你的应用只是“打开一个网页全屏显示”,理论上Tauri体积优势很大。但现实是,Electron的坑我都踩熟,文档最全,遇到问题搜得到答案,交付时间又紧,稳妥比体积更重要。而且Electron对老Windows系统兼容性更好,工控机上很多还是Win7/Win10老版本,Tauri的WebView2在部分机器上需要单独安装运行时,容易被甲方现场卡住。
1.2 整体技术链路
我们最终做的方案是三层结构:
- 主进程(main.js):创建全屏窗口、隐藏菜单栏、管理Esc退出逻辑。
- 渲染进程(index.html):业务页面本体,大屏展示、滚动数据、可视化图表。
- 预加载脚本(preload.js):通过contextBridge向页面安全暴露“退出应用”之类的接口,页面也能触发程序退出。
之所以要留preload这一层,是因为新版Electron默认开启contextIsolation: true,渲染进程里不能直接拿ipcRenderer。新手最容易在这卡住:页面里写了require('electron'),结果报错。后面我会细讲。
2. 环境准备与最小项目骨架搭建
2.1 初始化项目
前提是你机器上装了Node.js,建议用LTS版本。然后建一个项目目录,在里面执行:
npm init -y npm install --save-dev electron electron-builderelectron-builder是用来打包的,别漏装。装完之后,目录结构大概是这样的:
project/ ├── build/ │ └── icon.ico # 打包用的应用图标 ├── main.js # 主进程 ├── preload.js # 预加载脚本 ├── index.html # 你的网页(可换成自己的业务页面) └── package.json2.2 最小主进程代码
main.js里是最核心的逻辑,我直接给一份能跑通的版本:
const { app, BrowserWindow, globalShortcut } = require('electron'); const path = require('path'); function createWindow() { const win = new BrowserWindow({ width: 1920, height: 1080, kiosk: true, // 关键:真正的全屏,遮住任务栏 autoHideMenuBar: true, // 隐藏菜单栏 webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false } }); win.loadFile('index.html'); } app.whenReady().then(() => { createWindow(); // 全局注册Esc快捷键直接退出 globalShortcut.register('Escape', () => { app.quit(); }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on('window-all-closed', () => { app.quit(); }); app.on('will-quit', () => { globalShortcut.unregisterAll(); });2.3 预加载脚本和页面
preload.js内容很短,它的作用是把“退出程序”这个能力安全地暴露给页面:
const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { exitApp: () => ipcRenderer.send('exit-app') });然后在main.js里补上对应的IPC监听:
const { ipcMain } = require('electron'); ipcMain.on('exit-app', () => { app.quit(); });index.html就正常写你的业务页面,如果页面里想提供一个“退出按钮”,可以在按钮点击事件里调用window.electronAPI.exitApp()。
这时执行npm start就能看到全屏窗口了。如果屏幕上哪都没露馅,说明骨架已经通了。
3. 核心需求攻关:全屏、遮任务栏、Esc退出的正确姿势
3.1 kiosk模式才是“真全屏”,fullscreen不是
很多教程只写fullscreen: true,但实测在Windows上,fullscreen模式偶尔会让任务栏浮在窗口上层,尤其是在Win10/Win11多显示器环境下。真正管用的是kiosk: true。
kiosk模式是专门给“信息亭”场景设计的,它的行为是:窗口占满整个屏幕、任务栏被完全遮挡、按F11/Alt+Tab等系统热键也无法轻易退出全屏。相比之下,fullscreen只是让窗口最大化到全屏,用户按F11还是能弹回窗口模式。
所以我的建议是:kiosk和fullscreen同时写上,双保险。实测在部分老显卡驱动上,单独设fullscreen偶发任务栏残留,两个一起写反而稳定。
如果你想彻底一点,还可以加frame: false去掉窗口边框,配合kiosk使用。但要注意,去掉边框后如果程序异常,没法通过系统按钮关闭,只能靠任务管理器或快捷键,现场调试会有点痛苦。
3.2 Esc退出的两种实现思路
方案A:主进程全局快捷键(推荐)
上面的代码里已经写了,用globalShortcut.register('Escape', ...)。这是在操作系统的层面注册了一个全局热键,不管焦点在页面、地址栏还是弹窗里,只要按Esc就会触发放出逻辑。
优点是最省事、最稳定,缺点是全局热键会屏蔽其他程序在同样按键上的操作——不过对kiosk场景来说这根本不是问题,反而更安全。
方案B:页面内捕获keydown事件
如果你需要在退出前弹一个确认框(比如“确认退出系统吗”),可以用这套流程:
// 页面脚本里 document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { e.preventDefault(); if (confirm('确定要退出系统吗?')) { window.electronAPI.exitApp(); } } });这个方案的好处是交互可控,但前提是页面必须获得键盘焦点。如果页面上有iframe、嵌入视频等元素抢焦点,Esc事件可能收不到。稳妥的做法是两种方案混用:全局快捷键负责强制退出,页面内的监听负责带确认的优雅退出。这两个方式可以并存,全局快捷键会在页面按键处理完后继续触发,所以要留意自己会不会误触。
3.3 防止“按Esc退出全屏但程序还在”
在kiosk模式下,有些版本的Electron允许Esc先退出全屏状态,然后程序还留在桌面。这时候如果你再按一次Esc,全局快捷键才会生效。用户感受就是“卡住了,好像退不掉”,体验很差。
解决方法是:在main.js里捕获窗口的全屏状态变化,一旦发现退出全屏,立刻重新进入kiosk模式,或者直接退出程序:
win.on('leave-full-screen', () => { win.setKiosk(true); });这种做法相当“粗暴”,但也是kiosk终端场景最稳的兜底逻辑。不管用户用什么花招离开全屏,系统都能拉回来,保证终端永远停留在应用里。
3.4 隐藏鼠标光标
全屏展示场景一般没人动鼠标,光标杵在屏幕上很碍眼。可以在CSS里处理:
* { cursor: none; }想更精细一点,就在主进程里监听mousemove事件,超过几秒没动就自动隐藏光标,动一下再显示。这个属于体验优化,看场景需要。
4. 修改图标与文件名,以及打包输出
4.1 制作.ico图标
Windows程序的图标必须是.ico格式,不能直接用png。electron-builder对图标的要求是至少256x256,建议直接做多尺寸的ico,包含16、32、48、256等常见尺寸。
生成工具我常用两个:
- 在线转换:iconfont官网的图标制作工具、tinypng等,传PNG直接导出ICO。
- 本地工具:GIMP安装ico插件,或者直接用ImageMagick命令行:
magick converter.png -define icon:auto-resize=256,128,64,48,32,16 app.ico做完之后把app.ico放到项目build/目录下,后面打包时指定路径。
4.2 package.json和electron-builder配置
在package.json里添加build字段,这是electron-builder的配置:
{ "name": "dashboard-exe", "version": "1.0.0", "description": "大屏展示终端程序", "main": "main.js", "scripts": { "start": "electron .", "dist": "electron-builder" }, "build": { "appId": "com.example.dashboard", "productName": "EnergyDashboard", "files": ["main.js", "preload.js", "index.html", "assets/**/*"], "win": { "target": ["nsis", "portable"], "icon": "build/icon.ico" }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, "createStartMenuShortcut": true } }, "devDependencies": { "electron": "^31.0.0", "electron-builder": "^24.13.3" } }这里解释几个关键字段:
productName:打包出来的exe文件名就是它,我设的EnergyDashboard,生成的就是EnergyDashboard Setup 1.0.0.exe。想要中文名也可以直接写中文。win.target:nsis是生成安装向导,portable是生成免安装的单文件exe。两个都写上,一次打包出两种产物,方便现场选择。win.icon:指向你准备好的ico文件。NSIS配置:oneClick设为false后,安装过程允许用户自己选安装路径。
然后执行:
npm run dist第一次运行会比较慢,因为要下载Electron的预编译包和NSIS工具链。国内网络如果一直失败,配置一下electron-builder的镜像源,在项目根目录创建.npmrc:
electron_mirror=https://npmmirror.com/mirrors/electron/ electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/这一步能解决80%的打包下载问题,实测很有效。
打包完成后,产物在dist/目录下。拿一台没装Node环境的Windows机器实测安装,正常启动就说明交付没问题。
顺带提一句,如果项目是Vue/React写的,打包流程完全一样:先把前端项目执行npm run build得到dist目录,把main.js里改为win.loadFile(path.join(__dirname, 'dist/index.html')),再把files配置指向dist/**/*即可。
5. 常见问题与调试实录
5.1 任务栏还是露出来了
这是被问得最多的一个问题。如果你设置了fullscreen: true但任务栏还露着,原因大概率是:窗口还没完成全屏过渡,任务栏残留了一瞬间;或者显示器缩放比例不是100%,导致窗口高度算错了。
一劳永逸的做法就是前面说的:kiosk: true+fullscreen: true+frame: false三件套一起上。如果还有问题,检查是不是远程桌面连接导致的,远程桌面下任务栏处理逻辑会不一样,只能现场关了远程再接显示器测试。
5.2 Esc没反应
分两种情况排查:
- 如果页面里有输入框,焦点在输入框内,
keydown事件能被页面捕获,但globalShortcut依然能生效。如果按了没反应,先检查主进程注册代码有没有执行,可以在注册后面加一行console.log(globalShortcut.isRegistered('Escape'))看输出。 - 如果是页面监听方案失效,大概率是焦点不在页面主体上,给
window加上focus事件强制把焦点拉回来。
5.3 打包后打开闪退
闪退最常见的原因是加载路径不对。electron .开发模式不会暴露的问题,打包后文件读取路径变得敏感。建议路径统一用path.join(__dirname, 'xxx'),别用相对路径字符串。还有一种可能是权限问题,比如程序装在Program Files目录下,读取外部配置文件失败,可以试试右键管理员权限运行,能跑通就是目录权限的锅。
5.4 页面加载白屏
白屏的根源一般是资源加载顺序或网络问题。loadFile加载本地文件基本不会白屏,如果加载的是远程URL(比如Vue项目单独部署在服务器上),启动时会有一段白屏时间,因为要等网络请求。我建议把前端资源全部打包进exe,禁用网络依赖,终端程序稳定性是第一位的。如果一定要加载远程URL,至少做个本地loading页,或者用did-finish-load事件做兜底。
5.5 杀毒软件误报
Electron打包的exe时不时会被Windows Defender或360报毒,因为安装包带自解压逻辑且没有数字签名。解决方法只有一个:花几千块买个代码签名证书,给exe签名。个人开发者图便宜不签名的后果就是甲方机器上每次都弹风险提示,很掉价。如果项目只是内网自用,可以在杀毒软件里加白名单。
6. 一些额外的进阶技巧
6.1 单实例锁
终端应用最怕用户手滑开了两个进程,两个窗口叠在一起,逻辑全部乱掉。加一个单实例锁:
const { requestSingleInstanceLock } = require('electron'); if (!requestSingleInstanceLock()) { app.quit(); } else { app.on('second-instance', () => { // 第二个实例启动时,把已有窗口带到前台 const win = BrowserWindow.getAllWindows()[0]; if (win) { win.setKiosk(true); win.focus(); } }); }6.2 开机自启
展厅终端一般要求通电后自动启动应用,省得人工操作。Electron可以写注册表实现自启:
const { app } = require('electron'); app.setLoginItemSettings({ openAtLogin: true, path: process.execPath });注意这条要在app.whenReady()之后调用。打包安装版时,NSIS配置里也可以勾选“开机启动”选项,两种方式二选一即可。
6.3 看门狗重启
如果你的网页里跑了定时器、流媒体、WebSocket连接,长时间跑下来页面可能会挂。最粗暴但有效的兜底方案:主进程定时检查页面是否无响应,若超过阈值强制刷新或重启窗口。
setInterval(() => { const win = BrowserWindow.getAllWindows()[0]; if (win && win.webContents.isCrashed()) { win.reload(); } }, 60 * 1000);这个方案不优雅,但是在无人值守的终端场景里,效果比任何监控系统都实在。我记得有一次大屏跑了三天三夜,凌晨WebSocket断了重连失败,页面直接假死,现场没人,最后还是靠这个定时器在1分钟内拉回来的。
6.4 国产系统分发
有些项目还要跑到国产Linux系统上(比如UOS、麒麟),Electron基础能力其实可用,只需在Linux上用electron-builder --linux打AppImage或deb包,窗口配置逻辑完全复用。不过仍需在目标系统上实测,国产Linux各家WebKit内核差异较大,个别系统需要额外处理字体渲染和硬件加速开关。
按这套流程走下来,从拿到HTML页面到交出能用的exe,熟练之后基本半小时能出一版。我个人的体验是:kiosk模式是最重要的那个开关,它决定了你的窗口“到底是不是全屏”;Esc退出的处理方式决定了这个程序的“安全感”;而图标的更换和文件名的设置,则是甲方判断“你做得专不专业”的第一印象。
最后再分享一个小技巧:交付前一定要在32位和64位的Windows虚拟机里各跑一遍,哪怕是同样的代码,不同机器的表现也可能完全不同。尤其是老型号的工控机,显卡驱动不支持硬件加速,Electron窗口会白屏或拖影。遇到这种情况,启动时加一行app.disableHardwareAcceleration()就能解决大半。
用Electron把网页转成exe,本质上是给前端开发者的能力做了一次“物理封装”。这套流程不复杂,但每一处细节都关系到现场的稳定交付。希望这篇能把踩过的坑都帮你填平,让你在做大屏、终端、展厅这类项目时少折腾几个通宵。
本文还有配套的精品资源,点击获取