最近在学习Electron,想写一篇笔记总结并记录以加深印象。
Electron的概念
- Electron是一个嵌入了chromium与node到自己的二进制包中的、可以利用js、html、css技术构建桌面应用的框架
从它的概念可以得知,Electron的桌面应用,可以利用前端技术来构建,同时需要结合浏览器与nodejs的特点-也就是它也可以运用浏览器的API并且可以编写nodejs代码。
知道了它的概念,我们就可以按照官方文档上的demo来进行逐步的学习
开始一个Electron程序
安装Electron的包
- 可以选择全局安装 npm install --g elcectron
- electron -v 出现版本号就算安装成功
- 创建项目安装
- mkdir electron-app
- cd electron-app
- npm install --save-dev electron
- npx electron -v 出现版本号就算安装成功
编写一个主程序
进入到electron-app的项目目录下,此时是一个空的文件夹
- npn init -y
- 创建一个main.js(与package.json中的main属性的值命名一样,寓意为该程序以当前文件作为主入口)
- 创建一个index.html,并在此文件夹下编写一些简单的页面,比如Hello World~ 我们在main.js中写入以下程序
const electron = require('electron')
const app = electron.app // 将这个app看成当前的整个应用
const BrowserWindow = electron.BrowserWindow // 顾名思义,浏览器的窗口
let mainWindow = null // 声明要打开的主窗口
// app的生命周期,在ready之后才可以使用BroswerWindow
//以下代码的含义就是应用准备好了之后,加载一个宽高为800px的窗口,这个窗口载入的是我们编写的index.html文件
app.on('ready', () => {
mainWindow = new BrowserWindow({
width: '800px',
height: '800px',
})
// 加载页面
mainWindow.loadFile('index.html')
mainWindow.on('closed', () => {
mainWindow = null
})
})
现在我们可以尝试运行这个主程序啦~
在当前项目下执行electron .
如果运行成功就会直接弹出一个有菜单的浏览器窗口,如下:
如果运行失败就会弹出一个错误提示,根据错误提示进行相应的代码调整,让然后再重新运行:
ps:如果不想每次运行electron .
,那么就配置package.json中的scripts属性"dev":"electron ."
这样我们就可以使用npm run dev
增加一点交互
我们实现了Hello Word,就在此基础上添加一点交互---在页面上点击【引入文本】的按钮,引入指定文件内容
例如,我们在html中写下如下代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Electron</title>
</head>
<body>
Hello World~
<button id="btn">引入文本</button>
<div id="text"></div>
// 引入处理交互的JS文件
<script src="./render/render1.js"></script>
</body>
</html>
在项目目录下创建一个render目录,在render中创建一个render1.js。我们在该js文件下写下如下代码:
// 渲染进程与主进程是两个不用的进程,它们之间是独立的,若想在渲染进程中使用node的api,则需要配置
// 渲染进程暂且理解成我们这个renders.js文件,主进程理解成main.js
const fs = require('fs')
window.onload = function () {
const btn = this.document.querySelector('#btn')
const text = this.document.querySelector('#text')
btn.onclick = function () {
fs.readFile('text.txt', (err, data) => {
console.log(err, data)
if (!err) {
text.innerHTML = data
}
})
}
}
再创建一个text.txt文本文件,随意添加一些内容,此时我们的目录结构应该是这样的:
运行electron .
正常打开窗口,点击【引入文本】发现并没有将内容引进来,Ctrl+Shift+i
打开调试器,发现如下错误:
正如我们在代码注释中所说,在render.js中是不能够使用node的api的,所以require是没有用的(渲染进程与主进程是相互独立的,暂且记住,后面会详细记录)
解决这个问题,只需要告诉当前操作的这个窗口,允许它使用node的api就可以了。于是我们在创建窗口的时候:
app.on('ready', () => {
mainWindow = new BrowserWindow({
width: '800px',
height: '800px',
webPreferences: { nodeIntegration: true, contextIsolation: false }, // 这里
})
...
})
这样我们再electron .
点击【引入文本】,就可以实现这个功能了
实现一个超链接(打开一个新窗口)
我们已经可以在应用中使用html,css,js以及nodejs实现一些页面上的需求,接下来看看如何在页面中使用超链接跳转到新的网页。
第一种情况:a标签链接到外部
<a id="aHref" href="http://www.baidu.com" target="_blank">去百度</a>
这时候点击a标签会新开一个当前app应用的窗口链接过去
如果想要实现,点击a标签就使用谷歌浏览器打开,需要用到shell
模块,shell模块是在渲染进程与主进程中都可以直接使用的shell文档。我们在render1.js
中添加如下代码:
const { shell } = require('electron')
const aHref = document.querySelector('#aHref')
aHref.onclick = function (e) {
e.preventDefault()
const href = this.getAttribute('href')
shell.openExternal(href)
}
这时候再点击a标签,就会在谷歌浏览器中打开了(修改过后重新运行electron .
)
第二种情况:点击一个元素,跳转到我们自己写的页面
- 首先我们先创建一个简单的页面
red.html
,可以先暂时放在项目目录下,将body的颜色设置成红色 - 我们需要在
index.html
中创建一个元素,然后为这个元素添加点击事件,新开窗口跳转到red.html
<button id="goNewWin">点击新开窗口</button>
- 在
render1.js
中编写该元素的点击逻辑
const { BrowserWindow } = require('@electron/remote')
window.onload = function () {
....
// 先获取这个元素
const goNewWin = this.document.querySelector('#goNewWin')
goNewWin.onclick = function () {
// 新开的窗口
let newWin = new BrowserWindow({
width: '500px',
height: '500px',
})
// 载入刚才编写的红色页面
newWin.loadFile('red.html')
// 窗口注销的时候,将这个window设置为空
newWin.on('closed', () => {
newWin = null
})
}
}
由于创建一个新的窗口是主线程才可以使用的api,我们不可以在render1.js
这个渲染进程中使用,所以必须用到@electron/remote
这个模块来帮助我们完成。使用的步骤如下:
npm install --save @electron/remote
文档- 在主进程中进行初始化
require('@electron/remote/main').initialize()
- 在渲染进程中引入使用
ps:在electron >= 14.0.0
的时候,我们还需要使用enable
api指定具体的要渲染的webContents,这个webContents通常使用我们所创建的窗口的webContents获得-mainWindow.webContents
// main.js
...
require('@electron/remote/main').initialize()
...
app.on('ready', () => {
...
require('@electron/remote/main').enable(mainWindow.webContents) // 这里
...
})
设置菜单
翻阅Electron文档就可以发现,在Main Process模块有一个Menu模块,我们要实现菜单的制定能够以配置,就需要借助它。
设置顶部菜单
我们在项目目录下新创建一个目录叫menu,在里面新创建一个menu.js
文件,写入以下代码:
const { Menu, BrowserWindow } = require('electron')
// 菜单是以数组形式展示的
const template = [
{
label: '第一项',
submenu: [
{
label: '子项1',
accelerator: 'Ctrl+n', // 绑定一个快捷键
click: () => { // 实现一个子菜单的点击事件
// 点击【子项1】新开一个页面
let win = new BrowserWindow({
width: 500,
height: 500,
webPreferences: { nodeIntegration: true, contextIsolation: false },
})
win.loadFile('red.html')
win.on('closed', () => {
win = null
})
},
},
{
label: '子项2',
},
],
},
{
label: '第二项',
submenu: [
{
label: '子项1',
},
{
label: '子项2',
},
],
},
{
label: '第三项',
submenu: [
{
label: '子项1',
},
{
label: '子项2',
},
],
},
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
菜单是一个数组,里面包含的对象是菜单的每一项。每一个对象有一些特定的属性用于配置菜单,例如label-配置菜单的名称,submenu-配置菜单的子项等等。写好模板之后,我们需要使用Menu的api-buildFromTemplate
,从定义好的模板中构建一个菜单,再使用Menu的setApplicationMenu
api将应用的菜单设置成我们构建好的菜单
这个独立的菜单文件编写好了之后,需要在main.js
中引用,在main.js
中引入添加以下代码:
// main.js
app.on('ready',()=>{ // 使用app.whenReady()也可以,并且更好,详见官方文档
....
require('./menu/menu')
...
})
这时候我们在终端输入electron .
,会发现顶部菜单变成了我们所设置的。
设置右键菜单(contextmenu)方法一
我们平时在页面中或是在系统中点击右键,都会出现一个右键的菜单。从这个行为可知,点击行为是发生在渲染进程的,也就是说我们需要在渲染进程添加contextmenu
的监听事件,如果发生了该点击行为,就创建出右键菜单。
// render1.js
const { BrowserWindow, Menu, getCurrentWindow } = require('@electron/remote')
// 第一步先创建一个右键菜单模板contextMenuTemplate
const contextMenuTemplate=[
{ label: '复制'},
{ label: '粘贴'},
]
// 第二步,使用模板生成一个Menu
const m = Menu.buildFromTemplate(contextMenuTemplate)
window.addEventListener('contextmenu',(e)=>{
e.preventDefault() // 阻止默认行为
// 弹出右键菜单
m.popup({
window:getCurrentWindow()
})
})
这里需要注意的是,men、getCurrentWindow等API都是Main Process中的,如果我们要在渲染进程中使用它,就要从@electron/remote
中引入
设置右键菜单(contextmenu)方法二
ipcRenderer
和ipcMain
- ipcRenderer是一个 EventEmitter 的实例,可以使用它提供的一些方法从渲染进程 (web 页面) 发送同步或异步的消息到主进程。 也可以接收主进程回复的消息。
- ipcMain是一个EventEmitter实例,当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息。 从渲染器进程发送的消息将被发送到该模块。
- ipcRender在渲染进程中引入并直接使用,ipcMain在主进程中引入并直接使用
// render1.js
window.addEventListener('contextmenu', e => {
e.preventDefault()
// 向主进程发送一个名为‘show-context--menu’事件
ipcRenderer.send('show-context-menu')
})
// main.js
// 处理渲染进程发送过来的事件
ipcMain.on('show-context-menu', event => {
const template = [
{
label: 'Menu Item 1',
click: () => {
// ...
},
},
{ type: 'separator' },
{ label: 'Menu Item 2', type: 'checkbox', checked: true },
]
const menu = Menu.buildFromTemplate(template)
// 在发送的位置弹出
menu.popup(BrowserWindow.fromWebContents(event.sender))
})
使用这个方法需要注意的是,渲染进程只向主进程发送消息,弹出菜单的事件需要主进程来处理(毕竟相关api都是在主进程中使用的)。主进程接收到“弹出菜单的消息”后,就在回调函数中处理弹出的逻辑。
打开一个子窗口并与它通信
创建一个子窗口内容sub.html与处理该子窗口内容的render2.js
// sub.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
我是子窗口
<button id="sendMessage">点击向父窗口发送信息</button>
</body>
<script src="./render/render2.js"></script>
</html>
//render2.js
window.onload = function () {
const sendMessage = document.querySelector('#sendMessage')
sendMessage.onclick = function () {
// 点击button,从子窗口向父窗口发送消息
window.opener.postMessage('从子窗口发送过来的消息')
}
}
// render1.js
...
// 监听子窗口发送过来的数据,监听window的message事件
window.addEventListener('message', msg => {
// msg是获取到的MessageEvent对象,收到消息后对其做后续的处理
const subMessage = document.querySelector('#subMessage')
subMessage.innerHTML = msg.data
})
...
在子窗口使用postMessage与在父窗口监听message事件,从而完成父子窗口之间的通信
应用断网功能提醒
实现这个其实非常简单,我们只需要监听online
与offline
的事件就可以实现
window.addEventListener('online', e => {
// 做恢复网络逻辑处理
console.log(e, 'online')
})
window.addEventListener('offline', e => {
// 做断网逻辑处理
console.log(e, 'offline')
})
应用底部消息通知
方法一
直接使用window.Notification(title,options)
的api
方法二
使用electron中主线程的Notification模块,示例:
// 在渲染进程中使用
const { Notification} = require('@electron/remote')
...
new Notification({
title: '我是electron的notification',
body: '我是通知的消息主体,消息主体',
}).show()
...
注册全局的快捷键
// 注册一个全局的快捷键,返回一个布尔值,true-注册成功,false-注册失败
const ret = globalShortcut.register('Ctrl+d', () => {
// 处理按下Ctrl+d的逻辑
})
if (!ret) {
// 注册失败的处理逻辑
}
// 检查快捷键是否注册成功,true-成功 false-失败
const isRegister = globalShortcut.isRegistered(ret)
// 注销快捷键
app.on('will-quit', () => {
// 单个注销
globalShortcut.unregister('Ctrl+d')
// 注销所有
globalShortcut.unregisterAll()
})
至此,已经可以搭建起一个简单的electron项目并进行编写相关的内容。还有一部分本文未提到的知识,例如:dialog
clipboard
等等,官方文档其实很详细,加油学习吧~