文章目录
介绍
electron技术架构:chromium、node.js、native.apis
electron工作流程
桌面应用就是运行在不同操作系统上的软件,软件中的功能是通过native.apis跟不同操作系统进行交互实现的,想实现什么功能调用响应的API即可
electron主要有两类进程:
- 主进程:main process
- 渲染进程:renderer process
当我们启动一个APP时,他首先会启动主进程,一般是main.js或index.js中的代码,主进程启动成功后会创建一个native ui,然后在nativeui里创建一个或多个Browse window,用于呈现界面(即web界面),每个Browse window可以看作是一个渲染进程,每个渲染进程相互独立,各自运行在自己的沙箱环境中,但是app在使用的过程中,不同窗口会进行交互,于是electron提供了IPC、RPC通信机制供窗口与窗口进行数据传输
只有主进程能操作原生API,能管理所有WEB界面,和这些web界面对应的渲染进程
渲染进程支持所有的DOM操作、node api的调用操作
环境搭建
package.json中main字段指定的文件就是app启动后,启动的主进程;script字段指定的是启动项目的命令
electron生命周期(app的生命周期)
ready:app初始化完成后会被调用一次,一般用于窗口初始化
dom-ready:一个窗口中的文本加载完成,与dom操作相关,有个webcontext对象可以调用
did-finsh-load:导航完成时触发,即选项卡的旋转器停止旋转时触发,且指派了onload事件。由webcontext调用
window-all-closed:所有窗口都被关闭时触发,如果我们没有监听这个方法,那么所有窗口关闭后,应用程序就默认退出,如果我们监听了这个方法,我们就可以自己决定所有窗口关闭后,应用程序是否退出,如果我们选择不退出,那么后续的before-quit、will-quit、quit都失效了
before-quit:在关闭窗口之前触发
will-quit:在窗口关闭并且应用退出时触发
quit:所有窗口被关闭时触发
closed:当窗口关闭时触发,此时应删除窗口引用
const { app, BrowserWindow } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
width: 800,
height:400
})
mainWindow.loadFile('index.html')
// webContents用于操作dom
mainWindow.webContents.on('did-finish-load',() => {
console.log("33333------did-finish-load")
})
mainWindow.webContents.on('dom-ready',() => {
console.log("22222------dom-ready")
})
mainWindow.on('close',() => {
console.log("88888------close")
mainWindow = null
})
}
app.on('ready',() => {
console.log("11111------ready")
createWindow()
})
app.on('window-all-closed',() => {
console.log("44444------window-all-closed")
})
app.on('before-quit',() => {
console.log("55555------before-quit")
})
app.on('will-quit',() => {
console.log("66666------will-quit")
})
app.on('quit',() => {
console.log("77777------quit")
})
执行顺序:1——2——3——8——4——5——6——7
窗口尺寸
const { app, BrowserWindow } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
x:100,// 设置窗口显示的位置,距离左边的距离
y:100,// 设置窗口显示的位置,距离右边的距离
width: 800,
height:400,
maxHeight:600,// 窗口最大高度
minHeight:200,// 窗口最小高度
maxWidth:1000,// 窗口最大宽度
minWidth:300,// 窗口最小宽度
resizable:false// 是否允许缩放窗口,默认为true,可以缩放窗口,设为false
})
mainWindow.loadFile('index.html')
mainWindow.on('close',() => {
console.log("88888------close")
mainWindow = null
})
}
app.on('ready',createWindow)
app.on('window-all-closed',() => {
console.log("window-all-closed")
app.quit()
})
注意此时有个bug:窗口弹出来,然后有一段白屏时间,然后再出现内容,这是因为let mainWindow = new BrowserWindow()
执行完成后就会显示出窗口来,此时窗口里面是没有内容的,我们设置show:false
,让窗口创建好也不展示出来,然后加载index.html文件,加载完成后,监听窗口的ready-to-show
方法,再把窗口展示出来
const { app, BrowserWindow } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
show:false,
width: 800,
height:400
})
mainWindow.loadFile('index.html')
// 监听窗口已经准备好去展示了
mainWindow.on('ready-to-show',()=>{
mainWindow.show()
})
mainWindow.on('close',() => {
console.log("88888------close")
mainWindow = null
})
}
app.on('ready',createWindow)
app.on('window-all-closed',() => {
console.log("window-all-closed")
app.quit()
})
窗口标题
const { app, BrowserWindow } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
show:false,
width: 800,
height:400,
frame: false,// 是否有边框
transparent:true,// 窗体透明,只有frame: false时才生效
icon:'lg.ico',// 图标
title:'拉钩教育',// 窗口标题
autoHideMenuBar: true// 隐藏菜单、选项卡
})
mainWindow.loadFile('index.html')
// 监听窗口已经准备好去展示了
mainWindow.on('ready-to-show',()=>{
mainWindow.show()
})
mainWindow.on('close',() => {
console.log("88888------close")
mainWindow = null
})
}
app.on('ready',createWindow)
app.on('window-all-closed',() => {
console.log("window-all-closed")
app.quit()
})
需求:窗口里面提供一个按钮,点击按钮,再弹出一个窗口
ctr+r可以对应用进行重载
25版本后可以使用ipcMain和ipcRenderer实现新窗口
index.html代码如下:
版本一:主进程允许渲染进程使用BrowserWindow实现,main.js代码如下:
const { app, BrowserWindow } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
show:false,
width: 800,
height:400,
webPreferences:{
nodeIntegration:true,// 允许渲染进程使用node集成环境
contextIsolation: false,// 如果nodeIntegration:true失效,那么需要添加这行代码
}
})
mainWindow.loadFile('index.html')
// 监听窗口已经准备好去展示了
mainWindow.on('ready-to-show',()=>{
mainWindow.show()
})
mainWindow.on('close',() => {
console.log("88888------close")
mainWindow = null
})
}
app.on('ready',createWindow)
app.on('window-all-closed',() => {
console.log("window-all-closed")
app.quit()
})
index.js代码(index.html的js代码)如下:
版本二:主进程允许渲染进程使用remote实现,main.js代码如下:
const { app, BrowserWindow } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
show:false,
width: 800,
height:400,
webPreferences:{
enableRemoteModule:true//允许使用远程模式
},
autoHideMenuBar: true// 隐藏菜单、选项卡
})
mainWindow.loadFile('index.html')
// 监听窗口已经准备好去展示了
mainWindow.on('ready-to-show',()=>{
mainWindow.show()
})
mainWindow.on('close',() => {
console.log("88888------close")
mainWindow = null
})
}
app.on('ready',createWindow)
app.on('window-all-closed',() => {
console.log("window-all-closed")
app.quit()
})
index.js代码(index.html的js代码)如下:
const { remote } = require('electron')
window.addEventListener('DOMContentLoaded',() => {
// 点击按钮打开一个新窗口
const oBtn = document.getElementsById('btn')
oBtn.addEventListener('click',() => {
// 创建窗口
let indexWindow = new remote.BrowserWindow({
width:200,
height:200
})
indexWindow.loadFile('sub.html')
indexWindow.on('close',() => {
indexWindow = null
})
})
})
自定义窗口的实现
需求:点击窗口右上角的三个图标,分别执行相应操作
index.html代码如下,红框中为三个图标的代码
main.js中代码如下:
const { app, BrowserWindow } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
show:false,
width: 800,
height:400,
webPreferences:{
nodeIntegration:true,// 允许渲染进程使用node集成环境
contextIsolation: false,// 如果nodeIntegration:true失效,那么需要添加这行代码
enableRemoteModule:true//允许使用远程模式
},
autoHideMenuBar: true// 隐藏菜单、选项卡
})
mainWindow.loadFile('index.html')
// 监听窗口已经准备好去展示了
mainWindow.on('ready-to-show',()=>{
mainWindow.show()
})
mainWindow.on('close',() => {
console.log("88888------close")
mainWindow = null
})
}
app.on('ready',createWindow)
app.on('window-all-closed',() => {
console.log("window-all-closed")
app.quit()
})
index.html的js代码写在index.js中,如下:
const { remote } = require('electron')
window.addEventListener('DOMContentLoaded',() => {
// 利用remote可以获取当前窗口对象
let mainWindow = remote.getCurrentWindow()
// 获取元素添加点击操作的监听
let aBtn = document.getElementsByClassName('windowTool')[0].getElementsByTagName('div')
aBtn[0].addEventListener('click',() => {
// 关闭窗口
mainWindow.close()
})
aBtn[1].addEventListener('click',() => {
// 最大化
console.log(mainWindow.isMaximized())
// 先判断当前窗口是不是最大化,如果是,则回到原来的大小,如果不是最大化,则最大化当前窗口
if(!mainWindow.isMaximized()){
mainWindow.maximize()// 让当前窗口最大化
}else{
mainWindow.restore()// 让当前窗口回到原来的大小
}
})
aBtn[2].addEventListener('click',() => {
// 最小化
console.log(mainWindow.isMinimized())
if(!mainWindow.isMaximized()){
mainWindow.minimize()// 让当前窗口最小化
}
})
})
阻止窗口关闭
点击右上角的关闭按钮后,弹出浮窗提示用户是否关闭,若用户选择关闭,则关闭应用,否则关闭浮窗,index.html页面如下:
index.html中的js代码保存在index.js中,代码如下:
const { remote } = require('electron')
window.addEventListener('DOMContentLoaded',() => {
// 监听mainWindow.close()
window.onbeforeunload = function(){
let oBox = document.getElementsByClassName('isClose')[0]
oBox.style.display = 'block'// 弹窗出现
let yseBtn = oBox.getElementsByTagName('span')[0]
let noBtn = oBox.getElementsByTagName('span')[1]
yseBtn.addEventListener('click', () => {
// 此时关闭窗口需要用destory,
// 因为onbeforeunload用于监听mainWindow.close(),如果这里还用mainWindow.close关闭的话,会陷入死循环
mainWindow.destory()
})
noBtn.addEventListener('click', () => {
oBox.style.display = 'none'// 去掉弹窗
})
}
// 利用remote可以获取当前窗口对象
let mainWindow = remote.getCurrentWindow()
// 获取元素添加点击操作的监听
let aBtn = document.getElementsByClassName('windowTool')[0].getElementsByTagName('div')
aBtn[0].addEventListener('click',() => {
// 关闭窗口
mainWindow.close()
})
aBtn[1].addEventListener('click',() => {
// 最大化
console.log(mainWindow.isMaximized())
// 先判断当前窗口是不是最大化,如果是,则回到原来的大小,如果不是最大化,则最大化当前窗口
if(!mainWindow.isMaximized()){
mainWindow.maximize()// 让当前窗口最大化
}else{
mainWindow.restore()// 让当前窗口回到原来的大小
}
})
aBtn[2].addEventListener('click',() => {
// 最小化
console.log(mainWindow.isMinimized())
if(!mainWindow.isMaximized()){
mainWindow.minimize()// 让当前窗口最小化
}
})
})
父子及模态窗口
index.js代码如下:
const { remote } = require('electron')
window.addEventListener('DOMContentLoaded',() => {
// 点击按钮打开一个新窗口
const oBtn = document.getElementsById('btn')
oBtn.addEventListener('click',() => {
// 创建窗口
let indexWindow = new remote.BrowserWindow({
parent:remote.getCurrentWindow(),// 指定子窗口的父窗口是主线程的窗口
modal:true,// 子窗口是模态化窗口,当子窗口出现时,父窗口不能游任何操作,也不能移动
width:200,
height:200
})
indexWindow.loadFile('sub.html')
indexWindow.on('close',() => {
indexWindow = null
})
})
})
自定义菜单
main.js中代码如下:
// 1. 导入menu模块
const { app, BrowserWindow,Menu } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
title:"自定义菜单",
show:false,
width: 800,
height:400,
webPreferences:{
nodeIntegration:true,// 允许渲染进程使用node集成环境
enableRemoteModule:true//允许使用远程模式
},
})
// 2. 定义自己需要的菜单项
let menuTemp = [
{
label:"文件",// 一级菜单
submenu:[// 二级菜单
{
label:"打开文件",
click(){// 点击该菜单要执行的逻辑
console.log("111111")
}
},
{type:"separator"},// 对二级菜单进行分类的分隔符
{label:"关闭文件",},
{
label:"关于",
role:"about"// 内部集成了一些功能(如复制、粘贴等),可通过role指定相应的值来使用
},
]
},
{label:"编辑"}
]
// 3. 利用上述模板生成一个菜单项
let menu = Menu.buildFromTemplate(menuTemp);
// 4. 将上述自定义菜单添加到应用里
Menu.setApplicationMenu(menu);
mainWindow.loadFile('index.html')
// 监听窗口已经准备好去展示了
mainWindow.on('ready-to-show',()=>{
mainWindow.show()
})
mainWindow.on('close',() => {
mainWindow = null
})
}
app.on('ready',createWindow)
app.on('window-all-closed',() => {
app.quit()
})
electron中针对不同的操作系统调用的API可能不一样,通过
process.platform
可以查看当前操作系统
菜单角色及类型
// 1. 导入menu模块
const { app, BrowserWindow,Menu } = require('electron')
// 创建窗口
function createWindow() {
let mainWindow = new BrowserWindow({
title:"自定义菜单",
show:false,
width: 800,
height:400,
webPreferences:{
nodeIntegration:true,// 允许渲染进程使用node集成环境
enableRemoteModule:true//允许使用远程模式
},
})
// 2. 定义自己需要的菜单项
let menuTemp = [
{
label:"角色",// 一级菜单
submenu:[// 二级菜单
{
label:"复制",
role:"copy"
},
{
label:"粘贴",
role:"copy"
},
{
label:"剪切",
role:"paste"
},
{
label:"最小化",
role:"minimize"
},
]
},
{
label:"类型",
submenu:[// 二级菜单
{
label:"选项1",
type:"checkbox"
},
{
label:"选项2",
type:"checkbox"
},
{
label:"选项3",
type:"checkbox"
},
{type:"separator"},
{
label:"item1",
role:"radio"
},
{
label:"item2",
role:"radio"
},
{type:"separator"},
{
label:"windows",
type:"submenu",
role:"windowMenu"
},
]
},
{
label:"其他",
submenu:[
{
label:"打开",
icon:"./open.png",// 设置图标
accelerator:"ctrl+o",// 设置快捷键
click(){
console.log("open操作执行了!!")
}
}
]
}
]
// 3. 利用上述模板生成一个菜单项
let menu = Menu.buildFromTemplate(menuTemp);
// 4. 将上述自定义菜单添加到应用里
Menu.setApplicationMenu(menu);
mainWindow.loadFile('index.html')
// 监听窗口已经准备好去展示了
mainWindow.on('ready-to-show',()=>{
mainWindow.show()
})
mainWindow.on('close',() => {
mainWindow = null
})
}
app.on('ready',createWindow)
app.on('window-all-closed',() => {
app.quit()
})
role:"windowMenu"
如下:
icon:"./open.png",
设置图标如下:
动态创建菜单
实现功能:
- 点击创建自定义菜单,将原来的菜单替换为新的自定义菜单
- 输入自定义菜单项内容后,点击添加菜单项,将输入的自定义菜单项添加在菜单栏中
index.html代码如下:
index.js(渲染进程,所以主进程main.js中创建窗口时要开启:enableRemoteModule:true
)代码如下:
const { remote } = require('electron');
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;
window.addEventListener('DOMContentLoaded',()=>{
// 获取相应的元素
let addMenu = document.getElementById("addMneu");
let menuCon = document.getElementById("menuCon");
let addItem = document.getElementById("addItem");
// 自定义全局变量存放菜单项
let menuItem = new Menu();
// 生成自定义的菜单
addMenu.addEventListener("click",()=>{
// 创建菜单
let menuFile = new MenuItem({label:"文件",type:"normal"});
let menuEdit = new MenuItem({label:"编辑",type:"normal"});
let customMenu = new MenuItem({label:"自定义菜单项",type:"normal"});
// 将创建好的自定义菜单添加至menu
let menu = new Menu();
menu.append(menuFile);
menu.append(menuEdit);
menu.append(customMenu);
// 将menu放置于app中显示
Menu.setApplicationMenu(menu);
})
// 动态添加菜单项
addItem.addEventListener("click",()=>{
// 获取当前input输入框当中的内容
let con = menuCon.value.trim();
if(con){
menuItem.append(new MenuItem(label:con,type:"normal"))
menuCon.value="";// 清空输入框的内容
}
})
})
自定义右键菜单
实现功能:右击窗口,弹出自定义菜单:
index.html代码如下:
index.js代码如下:
const { remote } = require('electron');
const Menu = remote.Menu;
// 定义菜单的内容
let contextTemp = [
{label:"run code"},
{label:"转到定义"},
{type:"separator"},
{
label:"其他功能",
click(){
console.log("其他功能选项被点击了!!")
}
}
]
// 依据上述内容来创建menu
let menu = Menu.buildFromTemplate(contextTemp)
// 鼠标右击监听
window.addEventListener("DOMContentLoaded",()=>{
window.addEventListener("contextmenu",(ev)=>{
ev.preventDefault();// 阻止原生事件
menu.popup({// 弹出窗口
window: remote.getCurrentWindow()// 配置具体弹出哪个窗口,remote.getCurrentWindow()获取当前远程窗口
},false)
})
})
在窗口中打开开发者工具可以看到控制台输出了:其他功能选项被点击了!!
主进程与渲染进程
主进程与渲染进程间通信
一、实现:渲染进程发送消息触发主进程的行为
index.html代码如下:
主进程使用ipcMain.on("事件名",监听到事件后执行的方法)
监听渲染进程发送的消息,main.js代码如下:
渲染进程使用ipcRenderer.send("事件名",参数)
向主进程发送一条【异步】消息
渲染进程使用ipcRenderer.sendSync("事件名",参数)
向主进程发送一条【同步】消息,因为是同步消息,所以渲染进程会等到主进程返回后再执行之后的代码
主进程使用ev.returnValue
向渲染进程返回信息
主进程使用
ipcMain.send("事件名",参数)
或者e v.sender.send("事件名",参数)
触发渲染进程的事件
二、实现:主进程发送消息触发渲染进程的行为
在主进程中自定义一个菜单并替换掉原有的菜单:
每个窗口都有个webContents,用于控制当前窗口的所有内容。主进程中使用BrowserWindow.getfovusedWindow
获取正在显示的渲染进程窗口,BrowserWindow.getfovusedWindow.webContents
获取正在显示的渲染进程窗口的所有内容,BrowserWindow.getfovusedWindow.webContents.send("事件名",参数)
主进程向渲染进程发送一个事件
渲染进程中使用
getCurrentWindow
用于获取正在显示的渲染进程窗口
渲染进程使用ipcRenderer.on("事件名",接收到事件后执行的方法)
监听事件
渲染进程间通信
不同进程运行在不同的沙箱环境中,想要通信可以借助主进程,也可以把数据放在公共可以访问的地方,如localStorage
一、实现:渲染进程1向主进程发送一个事件并将数据保存在localStorage中,主进程接收到后,打开窗口2(渲染进程2),在窗口2中使用localStorage中的数据。
渲染进程1的代码index.js:
主进程main.js代码:
渲染进程2的html代码subWin1.html:
渲染进程2的代码subWin1.js:
现在有个问题:subWin1.html与主窗口不相关,关闭主窗口subWin1.html并不会关闭,这是因为subWin1.html不是主窗口的子窗口,当然也可以把subWin1.html做成模态窗口(此处展示将subWin1.html做成子窗口)。在主进程定义一个变量mainId,用于存放主窗口的id(每个窗口都有一个唯一的id属性),在主窗口创建subWin1时,为其parent属性指定其父窗口的id,即主窗口的id
BrowserWindow.fromId("窗口id")
通过窗口id获取窗口,类似于document.getElementById
基于本地存储的渲染进程通信
一、实现:渲染进程1向主进程发送一个事件,主进程接收到后,打开窗口2(渲染进程2),在窗口2中发送数据给渲染进程1。
窗口2中先新增一个按钮
窗口2(subWin1.js)使用ipcRenderer.send("事件名",参数)
发送事件给主进程
主进程(main.js)使用ipcMain.on("事件名",接收到事件后执行的方法)
接收事件,并根据窗口id获得窗口1的id,然后使用该窗口的webContents.send("事件名",参数)
发送事件
窗口1接收事件
二、实现:窗口1通过主进程发送数据给渲染进程2
窗口1(index.js)使用ipcRenderer.send("事件名",参数)
发送事件给主进程
因为窗口2是主进程在接收到窗口1的事件后创建的(见前述),所以主进程在创建窗口2的时候,就可以获得窗口2的实例,主进程直接监听窗口2加载完成的事件,监听到了就向窗口2发送事件和数据
窗口2接收事件
dialog模块
实现:点击窗口按钮,弹出dialog。index.html中新增两个按钮,代码如下:
dialog属性详见官方文档
- title:dialog标题
- properties:dialog相关属性,如openFile选择文件、openDirectory选择文件夹、multiSelections多选
- filter:指定一个文件类型数组,用于规定用户可见或可选的特定类型范围。dialog右下角可选项(见本节最后一张图)
同步dialog和异步dialog属性一样,异步dialog返回一个promise,所以我们可以使用.then()
或.catch()
捕捉他
shell与iframe
一、shell:采用默认应用(通过调用不同操作系统)管理文件、文件夹、URL。
webview标签基于chrome浏览器内核,该内核的架构还在变化,所以webview不稳定,建议使用iframe代替webview
二、实现:shell操作文件夹、URL。
在index.html中添加两个按钮
使用shell.openExternal(url)
打开URL
引入path,使用shell.showItemInFolder(文件夹路径)
打开文件夹
三、实现:使用iframe实现窗口中加入原生界面。index.html中代码:
样式:
消息通知
一、应用的消息通知:在使用应用的时候,经常会在操作系统右下角弹出应用给出的消息通知,在electron中,可以借助h5的notification来实现。index.html中新增一个按钮,点击后右下角弹出消息通知,代码:
main.js中使用window.notification(标题,配置项对象)
弹出消息通知,代码: