什么是 Electron
Electron(原名为Atom Shell)是GitHub开发的一个开源框架。是一个使用 JavaScript、HTML 和 CSS 构建跨平台的桌面应用程序框架。它基于 Node.js(后端) 和 Chromium(前端),被 Atom 编辑器和许多其他应用程序使用。Electron 兼容 Mac、Windows 和 Linux,可以构建出三个平台的应用程序。
为什么选择 Electron
有很多可以开发桌面应用的框架,比如Tauri 、Flutter 、Electron等,
- Tauri:打出来的包非常小,需要一定的 Rust 基础;
- Flutter:一套代码通吃 web、iOS、Android、macOS、Windows、Linux 六大平台,需要学习新的语言 Dart;
- Electron:纯 JavaScript 技术栈,生态非常成熟,前端同学快速上手,几乎无学习成本。
生态圈壮大,案例成熟且丰富
Electron生态很强大,有各种npm包和github库为开发应用程序提供解决方案。
Electron做桌面的案例也非常成熟了, Apps users love, built with Electron,其中包含很多很常用的应用,例如 Postman、VScode 等等。
前端工程师入门快
- 基于 Node.js: 这就意味着,Node 这个大生态下的模块,Electron 都可以使用。
- 跨平台: 可以同时开发 Web 应用和桌面应用,共享 UI、代码等资源,大大减少了工作量。
如何学习 Electron
Electron 是 Web 技术和 Node.js 技术的合体,对前端同学来讲非常容易上手。
Web 用于构建 UI 界面,这和平时我们用 React 或 Vue 开发项目没有任何区别,Electron 内置一个 chromium 浏览器,会启动一个渲染进程,把页面展示出来。
Node.js 则用于在主进程中调用 Electron 封装好的 API 来创建窗口、设置菜单、添加托盘、自定义协议、消息通知等。
第一章 工程搭建
第一步:项目初始化
安装以来
bash
mkdir electron-demo
cd electron-demo
yarn init -y
yarn add electron --dev
安装时遇到 Electron无法从淘宝镜像下载安装,报错HTTPError Response code 404 (Not Found)的问题
项目启动
在 package.json 增加启动配置项
package.json
"scripts": {
"start": "electron ."
},
第二步:创建应用程序
在项目根目录下创建一个页面 index.html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
</body>
</html>
想要将👆它加载进应用窗口中,可以通过electron
提供的app
和BrowserWindow
两个模块来实现。
app
模块,控制应用程序的生命周期。app.whenReady
等待应用程序就绪BrowserWindow
模块,创建和管理应用程序窗口。
在根目录下创建入口文件index.js
作为应用程序的入口文件,通过 loadFile
将index.html
加载进一个BrowserWindow
实例中。
loadUrl(url)
用来加载url
可以是远程地址 (例如http://
),也可以是file://
协议的本地HTML文件的路径.loadFile(filePath)
filePath
是一个与应用程序的根路径相关的HTML文件路径
index.js
const { app, BrowserWindow } = require('electron')
app.whenReady().then(() => {
// 等待应用程序就绪后再创建浏览器窗口
// 只有app模块的 ready 事件被激发后才能创建浏览器窗口
createWindow()
})
const createWindow = () => {
// 创建一个 BrowserWindow 实例
const win = new BrowserWindow()
// 将 index.html 加载到一个新的 BrowserWindow 实例中
win.loadFile('index.html')
}
此时,执行 yarn start
启动项目会弹出浏览器弹窗,加载的是 index.html 内容。
热更新
修改 package.json 中的 "start": "nodemon --watch index.js --exec electron ."
再次运行
npm run start
,当index.js
内容变化时,就会自动重新执行electron .
来重启应用。
第二章 开始实现功能
下面将主要介绍创建窗口、自定义协议、应用菜单、进程间通讯、消息通知几个常用模块。
1、创建窗口
通过 new BrowserWindow()
方法来创建一个窗口实例时,窗口属性 options 有很多:
width
窗口的宽度(以像素为单位)。 默认值为 800height
窗口的高度(以像素为单位)。 默认值为 600x
窗口相对于屏幕左侧的偏移量,默认值为将窗口居中y
窗口相对于屏幕顶端的偏移量,默认值为将窗口居中useContentSize
width 和 height 将设置为 web 页面不包含边框的尺寸, 这意味着窗口的实际尺寸(包括窗口边框的大小)会稍微大一点。默认值为false
center
窗口是否在屏幕居中,默认值为false
minWidth
minHeight
窗口的最小高度/高度,默认值为 0maxWidth
maxHeight
窗口的最大高度/高度,默认值不限resizable
窗口是否可以调整大小,默认值为true
。movable
窗口是否可移动,默认值为true
。macOS
Windows
minimizable
窗口是否可最小化,默认值为true
。macOS
Windows
maximizable
窗口是否可最大化,默认值为true
。macOS
Windows
closable
窗口是否可关闭,默认值为true
。macOS
Windows
focusable
窗口是否可以聚焦,默认值为true
,在 Windows 中设置 focusable: false 也意味着设置了 skipTaskbar: truealwaysOnTop
窗口是否应始终位于其他窗口之上。默认值为false
fullscreen
窗口是否应全屏显示。如果明确设置为false
macOS 上的全屏按钮将被隐藏或禁用。默认值无fullscreenable
窗口是否可以进入全屏模式。macOS 上表示最大化/缩放按钮是否可以切换全屏模式或最大化窗口,默认值为true
simpleFullscreen
在 macOS 上使用 pre-Lion 全屏。 默认值为 false,仅 macOS(没有试出来是什么功能)skipTaskbar
是否在任务栏显示窗口,默认为false
(没有试出来是什么功能)hiddenInMissionControl
当用户切换到任务控制时是否应该隐藏窗口,仅 macOS(没有试出来效果)title
窗口标题,默认为"Electron"
,<title>
标签设置的标题会覆盖这个属性show
窗口在创建后是否显示,默认为true
frame
设置为false
则会创建无边框窗口,默认为true
parent
指定父窗体,默认为nullmodal
是否为模态窗体,只会当窗体是子窗体的时候,这个值才起作用。acceptFirstMouse
点击 非活动窗口是否会穿透到 web contents,默认是false,仅 macOSdisableAutoHideCursor
是否在打字时隐藏光标,默认为false(没有试出来效果)autoHideMenuBar
是否自动隐藏菜单栏,除非按了Alt键,默认为false(没有试出来效果)enableLargerThanScreen
窗体是否能够比屏幕大,默认为falsee,仅 macOSbackgroundColor
窗体的背景颜色值,为十六进制数值,默认是#FFFhasShadow
是否有阴影,默认为trueopacity
设置窗口的初始透明度,在 0.0(全透明)和 1.0(完全不透明)之间 ,Windows
macOS
darkTheme
使用黑色主题,仅在 GTK+3的桌面环境工作,默认为falsetransparent
是窗体透明,默认为false,在Windows上,仅在无边框窗口下起作用type
窗体类型,默认是普通窗体titleBarStyle
窗体标题栏的样式,默认为defaultdefault
macOS Windows 的标准标题栏hidden
隐藏的标题栏,结果展示在完整大小的内容窗口。但在macOS内,标题栏样式仍然会暴露标准窗口左上方 的控制按钮("红绿灯")。hiddenInset
,隐藏的标题栏的另一种表现,比hidden
距离窗口边缘稍微嵌入一点,仅 macOScustomButtonsOnHover
,隐藏红绿灯,除非鼠标悬停在上面,experimental 实验性的,仅 macOS
trafficLightPosition
为无框窗口中的红绿灯按钮设置自定义位置,仅 macOSroundedCorners
无边框窗口在 macOS 上,是否应该有圆角。默认值为 true。 属性设置为 false ,将阻止窗口是可全屏的,仅 macOSthickFrame
Windows 上的无框窗口使用WS_THICKFRAME 样式,会增加标准窗口框架。设置为 false 时将移除窗口的阴影和动画。默认值为 true。vibrancy
增加一个振动效果到窗体上,仅 macOSbackgroundMaterial
设置 windows 窗口系统绘制的素材,可以设置auto
,none
,mica
,acrylic
ortabbed
,仅 windowszoomToPageWidth
默认是false。在macOS平台上,设置窗体上绿色按钮或者菜单栏上window-Zoom的菜单项行为。如果为true,则窗体将会扩展到适配网页页面内容的宽度。如果为false,则会扩展宽度到屏幕大小。这个值也将影响当调用 maximize() 方法的时候。tabbingIdentifier
macOS 选项卡组名称,允许使用原生选项卡打开窗口。Windows 中,有相同选项卡标识的将会组合在一起,这会添加一个原生新增选项卡按钮到你窗口的选项卡栏,同时 app 和窗口允许接收new-window-for-tab
事件。webPrefernces
设置网页功能设置 blog.csdn.net/qq_29069649...
创建无边框窗口
以下示例为打开一个初始位置在屏幕右上角 显示的,最小尺寸为 1100 * 900,无边框窗口:
main.js
function createWindow() {
// 获取屏幕的宽度
const screenWidth = screen.getPrimaryDisplay().workAreaSize.width;
// 定义窗口的宽高
const width = 1100;
const height = 900;
// 计算窗口的初始 x 坐标(屏幕宽度 - 窗口宽度)
const initialX = screenWidth - width;
if (win) {
win.setSize(width, height)
} else {
win = new BrowserWindow({
width,
height, // 设置窗口宽高
x: initialX, // 设置窗口的初始位置为屏幕右边缘
y: 0, // 设置窗口的初始位置为屏幕顶部
resizable: false, // 不允许用户调整窗口大小
minWidth: 800,
minHeight: 600,
titleBarStyle: 'hiddenInset', // 隐藏标题栏
frame: false, // 隐藏标题栏
enableLargerThanScreen: true, // 允许窗口大于屏幕
})
win.loadURL('https://juejin.cn/user/4476867080110957') // 加载页面
}
}
app.whenReady().then(() => {
createWindow();
})
- 当设置
frame: false
打开一个frameless无边框窗口时,窗口是不可拖动的调整位置的,需要通过 CSS 设置-webkit-app-region: drag;
来让指定区域可以拖动。-webkit-app-region: no-drag;
设置指定区域禁止拖动。 - macOS中,
titleBarStyle
支持设置为customButtonsOnHover
hiddenInset
- 在windows系统中
titleBarStyle
为hidden
,并且titleBarOverlay
为true
或者对象时,应用窗口也会默认显示出window操作系统的窗口控件工具(最大化、最小化、关闭)
创建模态窗口
main.js
const { BrowserWindow } = require('electron')
const top = new BrowserWindow()
const child = new BrowserWindow({
parent: top,
modal: true,
show: false
})
child.loadURL('https://github.com')
child.once('ready-to-show', () => {
child.show()
})
- 模态窗口是禁用父窗口的子窗口
- 要创建模态窗口,必须同时设置
parent
和modal
属性
2、单实例运行
在默认情况下,Electron 应用就是多实例的。例如
- 在 Windows 上,用户每双击一次 exe 就会开启一次应用;
- macOS 上如果应用已经启动,双击应用程序并不会重新启动应用,但是用户可以通过右键应用程序,选择显示包内容,双击
Contents/MacOS/
目录下的可执行文件,还是会启动一个新的实例。
如果想要应用最多只能创建一个实例,可以通过 requestSingleInstanceLock
抢占实例锁来实现。
main.js
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
}
requestSingleInstanceLock()
用于抢占实例运行锁,只有第一个启动的实例才返回 true,而一旦锁被强占之后,后续启动的其他实例再调用这个方法就会返回 false,此时执行 quit()
强制退出,来确保只有一个实例运行。
退出了第二个实例之后,在用户看来感觉像是没有唤醒应用,我们可以将已经启动的实例窗口显示到前台来,来优化这种体验。Electron 提供了 second-instance
事件来监听第二实例的启动行为,第一个实例可以在这里做出对应的行为,例如:将在后台的窗口显示出来,将最小化的窗口恢复。
main.js
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
mainWindow.restore() // 从最小化窗口恢复
mainWindow.show() // 从后台显示
}
3、自定义协议
桌面应用一般都有自己的协议,在浏览器地址栏输入应用协议头的链接,会弹出打开应用的弹窗,这就是通过注册自定义协议来实现的。
注册协议
给应用起一个唯一的协议名称,注册到系统中,这样就可以通过这个协议就可以唤醒应用了。Electron 是通过 setAsDefaultProtocolClient
方法将应用注册为协议(dog-clock://
)的处理器。
main.js
app.setAsDefaultProtocolClient('dog-clock');
注册完成后,当在浏览器中输入 dog-clock://
会弹出打开应用的提示,点击「打开」 就可以启动并打开应用了,这种方式就是 scheme 唤起。
获取 scheme 参数
在通过 scheme 唤醒应用时可以携带一些参数,获取到参数并进行后续的处理。dog-clock://
后边的参数用户可以完全自定义,例如模仿http地址query参数风格 dog-clock://helloworld?width=1280&height=700
在 macOS 和 Windows 上获取参数的方式不同。
- macOS 通过监听
open-url
事件来获取 url 参数 - windows 通过监听
second-instance
事件来获取参数
macOS
main.js
app.on('open-url', (_, url) => {
console.log('open-url: ', url); // open-url: dog-clock://helloworld?width=1280&height=700
})
windows 平台 通过 scheme 启动应用的时候,会作为启动参数传递给应用程序,通过 process.argv
来拿到所有参数,格式是数组,其中有一项就是 url
main.js
const url = process.argv.find(v => v.startsWith('dog-clock://'))
if (url) {
console.log('通过 scheme 唤起', url)
}
如果是再次唤醒应用,可以监听 second-instance
事件,其中第二个参数和 process.argv
类似,其中也包含着 url 参数
main.js
app.on('second-instance', (event, argv, workingDirectory) => {
const url = argv.find(v => v.startsWith('dog-clock://'))
if (url) {
console.log('通过 scheme 唤起', url)
}
}
4、设置菜单
从以下三种类型菜单分别介绍:应用内菜单、托盘菜单、右键菜单
设置应用内菜单项
Electron 中的 Menu
模块封装了菜单相关的各种方法,其中 buildFromTemplate
用于创建原生菜单,传入一个 MenuItem
对象数组,
main.js
// 菜单栏模板
const menuBar = [{
label: app.name,
submenu: [
{ label: '关于', role: 'about' },
{
label: '检测更新',
click: () => { checkUpdate() },
accelerator: 'Command+Ctrl+U',
},
{ type: 'separator' },
{ label: '隐藏 Dog Clock', role: 'hide' },
{ label: '隐藏其他应用', role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ label: '退出', accelerator: 'Command+Q', role: 'quit' }
]
}];
app.whenReady().then(() => {
// 构建菜单项
const menu = Menu.buildFromTemplate(menuBar);
// 设置一个顶部菜单栏
Menu.setApplicationMenu(menu);
})
MenuItem
对象有很多属性,其中最常用的有以下几个:
label
设置选项的标签名称accelerator
设置快捷键role
表示菜单项的角色,是Electron预定义的菜单项。如果设置了role
用户自己设置的 click 会被忽略掉 。更多role请参考type
表示菜单项的类型 可以是normal
,separator
,submenu
,checkbox
或radio
enabled
表示是否启用该项,这个选项可以动态的修改click
设置点击菜单后触发的方法submenu
:定义子菜单
设置系统菜单在 macOS 和 windows 系统中有略微差别:
- macOS 会自动设置第一个菜单,其菜单名与应用名相同,可以手动为 macOS 添加一个空白菜单
css
if (process.platform === 'darwin') {
menuTemplate.unshift({ label: '' })
}
- macOS 系统上,应用所有窗口都共享左上角的菜单
- Windows 和 Linux 系统是可以为 BrowserWindow 单独设置菜单
设置托盘菜单
在程序启动时,可以借助 Tray
模块来实现将应用程序加入到系统托盘,调用 setContextMenu
方法注册托盘菜单。
main.js
// 确保路径和格式正确,建议使用 16x16 像素或 32x32 像素的图标
const iconPath = path.join(__dirname, '../../build/icons/32x32.png')
// 实例化 tray 对象,需要在托盘中显示的图标url作为参数
const tray = new Tray(iconPath);
// 点击托盘图标的事件,根据窗口的显示状态切换主窗口的显示和隐藏
tray.on('click', () => {
if(win.isVisible()){
win.hide()
}else{
win.show()
}
})
const contextMenu = Menu.buildFromTemplate([
{ label: '退出', click: () => { app.quit() } }
])
// 设置右键托盘图标时的菜单,这里设置为只有一个退出选项
tray.on('right-click', () => {
tray.popUpContextMenu(contextMenu)
})
// 给托盘对象设置菜单
tray.setContextMenu(contextMenu)
// 设置鼠标移到托盘中的图标上时显示的文本
tray.setToolTip('这是一个小狗闹钟');
通过 tray.setContextMenu(contextMenu)
方式设置托盘菜单的话,无论点击还是右键都会显示设置的菜单。
win.isVisible()
返回boolean
表示窗口是否在应用程序前台对用户可见
设置右键菜单
在渲染页面中触发右键时,通知主进程弹出【复制】菜单。后面会详细介绍进程之间的通讯。
通过 Menu
的 buildFromTemplate
创建应用内菜单后,调用 popup
方法将菜单弹出。popup
接收以下参数:
window
:指定窗口(默认是当前聚焦的窗口)x
:菜单位置横坐标(相对于窗口的 x 轴偏移,默认是鼠标位置的横坐标)y
:菜单位置纵坐标(相对于窗口的 y 轴偏移,默认是鼠标位置的纵坐标)callback
:菜单关闭回调函数
index.html
const { ipcRendererSend } = window.electron;
window.addEventListener("contextmenu", (e) => {
e.preventDefault();
ipcRendererSend('mainWindow:contextMenu', e)
});
main.js
ipcMain.on('mainWindow:contextMenu', (_, arg) => {
const contextMenu = Menu.buildFromTemplate([
{ label: '复制', role: 'copy' }
]);
contextMenu.popup({
// window: BrowserWindow.getFocusedWindow(),
callback: () => { console.log('menu closed callback') },
});
})
Dock 菜单(macOS)
dock 栏菜单项也是通过 buildFromTemplate
来创建的,调用 Dock 模块提供的 dock.setMenu
方法来设置。
main.js
const menu = Menu.buildFromTemplate(menuTemplate)
app.dock.setMenu(menu)
5、进程间通信
Electron 应用程序区分主进程和渲染进程,进程间通信就是IPC(Inter-Process Communication)。
进程之间的通讯主要是通过ipcRenderer
和ipcMain
这两个模块实现的,其中ipcRenderer
是在渲染进程中使用,ipcMain
在主进程中使用。
如何在渲染进程中引入electron 的 ipcRenderer
模块呢?通过Electron窗口的preload方法引入预加载脚本。预加载(preload) 脚本先于网页内容开始加载,但执行在渲染器进程中。可以在 BrowserWindow
构造方法中的 webPreferences
选项中附加到主进程。
main.js
const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// ...
预加载脚本与浏览器共享同一个全局 Window
接口,并且可以访问 Node.js API,所以它在全局 window
中暴露任意 API 来给网页使用。
上下文隔离
上下文隔离功能将确保您的 预加载
脚本 和 Electron 的内部逻辑运行在 webcontent
网页之外的独立的上下文环境里。这样有助于阻止网站访问 Electron 的内部组件,以及预加载脚本中访问权限较高的API。
- 在 Electron 12 以上版本中
contextIsolation
(上下文隔离) 是默认开启的 - 可以通过
webPreferences.contextIsolation
来关闭上下文隔离。
main.js
win = new BrowserWindow({
webPreferences: {
contextIsolation: false,
preload: path.join(__dirname, 'preload.js')
},
});
上下文隔离禁用 直接在全局的 window
中暴露任意属性。
preload.js
// contextIsolation: false
window.myAPI = {
desktop: true,
doAThing: () => {
console.log('I did a thing')
}
}
上下文隔离启用 上下文隔离启用时需要通过contextBridge
模块可以用来安全地从独立运行、上下文隔离的预加载脚本中暴露 API 给正在运行的渲染进程。
preload.js
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
desktop: true,
doAThing: () => {
console.log('I did a thing')
}
})
渲染进程直接调用 无论上下文隔离是否开启,渲染进行的调用方式是相同的。
index.html
console.log(window.myAPI)
window.myAPI.doAThing()
渲染进程 到 主进程(单向)
上面我们创建过一个无边框的窗口,没有了系统的控制按钮,关闭窗口的功能就需要我们自行开发。当点击页面上的关闭按钮时,希望可以隐藏应用:
- 通过预加载脚本
preload
暴露ipcRendererSend: ipcRenderer.send
- 渲染进程向主进程发送
remindWindow:close
事件ipcRendererSend('remindWindow:close')
- 主窗口通过
ipcMain.on
监听remindWindow:close
事件
preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
ipcRendererSend: (channel, data) => {
ipcRenderer.send(channel, data);
}
})
index.html
const { ipcRendererSend } = window.electron;
const closeDom = document.querySelector('#control-buttons-close')
closeDom.addEventListener('click', (e) => {
ipcRendererSend('remindWindow:close')
})
main.js
ipcMain.on('remindWindow:close', () => {
console.log('remindWindow:close: ');
win.close();
});
主进程 到 渲染进程
- 主进程通过渲染进程的
webContents
实例向渲染进程发送消息。用法和渲染进程的ipcRenderer.send
类似。 - 渲染进程通过预加载脚本暴露的
ipcRendereReceive: ipcRenderer.on
来监听主进程发来的消息
main.js
win.loadFile(path.join(__dirname, '../index.html'))
win.webContents.send('message:something', '你好!我是主进程!')
preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
ipcRendereReceive: (channel, func) => {
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
})
index.html
ipcRendereReceive('message:something', (message) => {
console.log('message:something: ', message);
document.querySelector('.something').innerHTML =
`主进程发来的消息:${message}</span>`
})
渲染进程 到 主进程 (双向)
使用 invoke
和 handle
来实现渲染进程和主进程的双向通讯。
- 渲染器进程通过
invoke
发送消息并等待主进程的响应 - 主进程通过
handle
响应消息,并返回响应结果
preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
ipcRendereInvoke: (channel, args) => {
ipcRenderer.invoke(channel, args);
}
})
index.html
document.getElementById('get-windows').addEventListener('click', (e) => {
ipcRendereInvoke('mainWindow:windowsCount').then((count) => {
document.querySelector('.windows-count').innerHTML = count;
})
})
main.js
ipcMain.handle("mainWindow:windowsCount", (_) => {
const windows = BrowserWindow.getAllWindows()
return windows.length;
});
渲染进程 到 渲染进程
渲染进程之间通讯需要通过主进程作为中间者。例如,通知所有窗口改变主题,可以通过 BrowserWindow.getAllWindows
获取全部窗口实例,win.webContents.send
向所有窗口发送消息
main.js
ipcMain.handle("mainWindow:changeTheme", (_, type) => {
const windows = BrowserWindow.getAllWindows();
windows.forEach(win => {
nativeTheme.themeSource = type;
win.webContents.send('message:changeTheme', nativeTheme.themeSource);
// 不通知消息发送者窗口
// if (win.webContents !== event.sender) {}
});
return nativeTheme.themeSource;
});
6、消息通知
每个操作系统都有自己的机制向用户显示通知,提供了 Notification
可以用来实现。
系统通知
可以在电脑桌面弹出新消息通知,一般展示在电脑桌面的右上角。
主进程中显示通知
mail.js
function showNotification () {
new Notification({
title: '小狗提醒您!',
body: '现在时间8:00\n时间不早了,快回家去撸狗!',
silent: true, // 系统默认的通知声音
icon: path.join(__dirname, '../../build/icons/32x32.png'), // 通知图标
}).show()
}
app.whenReady().then(showNotification)
在渲染进程中显示通知
通知可以直接在渲染进程中使用 Web Notifications API 显示。
render.js
const openNotificationDom = document.getElementById('open-new-notification')
const outputDom = document.getElementById('output')
openNotificationDom.addEventListener('click', () => {
new window.Notification(
'小狗提醒您!',
{ body: '现在时间8:00\n时间不早了,快回家去撸狗!' }
).onclick = () => {
outputDom.innerText = '收到,正在飞奔回家🏃'
}
})
系统通知有一些局限性
- 系统通知本身受系统设置控制,电脑系统设置禁止弹出通知,那么用户将无法收到通知信息
- 样式单一,只有系统自带的样式
- macOS 与window 有一些不支持的功能,
new Notification([options])
如果想自定义消息通知,可以通过使用 BrowserWindow() 窗口来实现。设置好自定义通知的尺寸、位置、以及视图样式。
待解决问题
1、【已解决】macOS 系统菜单第一项设置label 但是显示还是【Electron】
以下方式均不生效:
package.json
中设置 name 和 productNameapp.setName('Dog-Clock')
修改appNameMenu.setApplicationMenu([{label: app.name])
修改默认菜单
可以手动为 macOS 添加一个空白菜单
2、【已解决】macOS 下设置启动图标
需要通过 app.dock.setIcon(iconPath)
方式设置
3、【已解决】win.loadFile 加载本地 html 页面的时候 style 标签不生效
需要给html文件设置内容安全协议:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline';">
第三章 打包应用
Electron Builder
安装依赖
npm i electron-builder --D
配置选项
设置配置项有两种方式:
- 第一种直接在
package.json
中添加build
选项进行配置; - 第二种是在根目录下创建文件
electron-builder.yml
。通常使用的还是直接在package.json
中进行配置。
package.json
"build": {
"appId": "com.dog-clock",
"directories": { // 输入输出目录相关的配置项
"output": "dist", // 打包生成的目录,默认是dist
"buildResources": "build" // 指定打包需要的静态资源,默认是build
}
},
"scripts": {
"build": "electron-builder",
}
在package.json
的scripts
添加指令 "pack": "electron-builder"
运行打包命令 npm run pack
electron-builder
会自动识别当前的操作系统,打出系统对应的安装包。
- Windows操作系统生成exe\msi;
- Mac操作系统生成dmg
下面是常用的配置项:
appId
:应用 idproductName
:应用名 "dog-clock"directories
:输入输出目录相关的配置项- buildResources: 指定打包需要的静态资源,默认是build
- output: 打包生成的目录,默认是dist。用来放置的是打包生成的各种文件
files
:用于指定哪些文件和文件夹应该被打包到最终的应用程序中mac
:macOS 系统下的专属配置target
: 安装包的格式,默认是"dmg"和"zip"
dmg
:macOS 系统下 dmg 安装包配置项background
:安装窗口背景图icon
:安装图标iconSize
:图标的尺寸window
:安装窗口的大小,{"width": 540,"height": 380}
win
:WindowsOS 系统下的专属配置target
: 安装包的格式,默认是"nsis"icon
:安装图标
nsis
:windowsOS 系统下 nsis 安装包配置项oneClick
:是否一键安装language
:安装语言,2052
对应中文allowToChangeInstallationDirectory
: 允许用户选择安装目录,默认为false
json
"build": {
"appId": "com.dog-clock",
"productName": "dog-clock",
"directories": {
"output": "dist",
"buildResources": "build"
},
"mac": {
"target": ["dmg","zip"]
},
"dmg": {
"background": "public/setup_background.jpg",
"icon": "build/icons/icon.icns",
"iconSize": 180,
"window": {
"width": 540,
"height": 380,
}
},
"win": {
"target": ["msi","nsis"],
"icon": "build/icons/icon.icns"
},
"nsis": {
"oneClick": false,
"language": "2052",
"perMachine": true,
"allowToChangeInstallationDirectory": true
}
}
生成应用图标
准备一张png图片,借助 electron-icon-builder
生成不同操作系统需要的不同尺寸的图标。Mac对应的格式为icns
,Windows对应的格式为ico
。
- 准备一张正方形的图片
icon.png
放在项目根目录的public
文件夹中。 - 安装依赖
npm i electron-icon-builder --D
- 添加命令
"build-icon": "electron-icon-builder --input=./public/icon.png --output=build --flatten"
打包工具分为主要有两个
-
官方提供的 Electron Packager 和 Electron Forge:这两个经常是配合在一起使用,因为 Electron Packager 只是将应用打包成可执行程序,而 Electron Forge 会继续将其打包成安装程序。
-
社区提供的 Electron Builder:这是目前最流行的打包工具,配置简单,开箱即用,使用也更广泛。覆盖 Windows、macOS 和 Linux 平台,并支持多种打包格式,集成了自动更新和代码签名的功能。下面就使用
electron-builder
来进行打包。
查看哪些文件被打包进app中
在打包后的文件夹中,有一个app.asar
压缩包,用来存放 Electron 应用程序的主业务文件,可以使用asar
工具将app.asar
解压查看都有哪些文件被打包了。
- 切换
app.asar
所在目录cd ./dist/mac-arm64/dog-clock.app/Contents/Resources/
- 解压文件
npx asar extract app.asar ./app-folder
- 哪些内容进行打包可以通过配置
package.json
中的files
字段来指定