1. 概述
Electron核心组成分为三个部分,Chromium,Node.js,Native apis。这三部分共同构成了Electron架构。
Chromium可以理解为浏览器,用于支持html,css,js页面构成。Native apis用于支持和操作系统的一些交互操作。Node.js用于操作文件等。
Electron可以看成一个框架,将chromium及Node.js整合到一起,它允许通过web技术构建程序,可以通过Native apis对操作系统进行一切操作。
桌面应用就是运行在不同操作系统中的软件,软件中的功能实现都是通过Native Api与操作系统的通信。操作系统对于前段来说基本相当于黑盒,想实现功能的时候只需要调用API就可以了,无需关注内部实现。
Electron内部存在不同的进程,一个是主进程,一个是渲染进程,当启动app的时候首先会启动主进程,主进程完成之后就会创建Native UI, 会创建一个或者多个 window,用window来呈现界面也就是web界面。每个window都可以看做一个渲染进程,各进程间相互独立,不同窗口数据可以通过rpc或者ipc通信,通信方法是封装好的,开发者直接使用就可以了。
app启动后主进程创建window窗口,然后win窗口会去加载界面也就是渲染进程,如果页面存在交互,需要将渲染进程中接收到的指令信息通过IPC通信给主进程,主进程收到信息之后通过调用操作系统实现功能,完成之后再通知给渲染进程。
主进程可以看做是package.json中main属性对应的文件。一个应用只能有一个主进程,只要主进程可以进行GUI的API操作。主进程可以管理所有的web界面和渲染进程。
渲染进程就是windows中展示的界面进程,一个应用可以有多个渲染进程,渲染进程不能直接访问原生API可以通过主进程访问。
2. 环境搭建
# 克隆示例项目的仓库
$ git clone https://github.com/electron/electron-quick-start
# 进入这个仓库
$ cd electron-quick-start
# 安装依赖并运行
$ npm install && npm start
main.js是主进程。
const { app, BrowserWindow } = require('electron')
const path = require('path')
// 创建窗口 并且加载页面 界面是运行在渲染进程的
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// 加载页面
mainWindow.loadFile('index.html')
mainWindow.on('close', () => {
console.log('当前窗口关闭')
})
}
// 生命周期
app.whenReady().then(() => {
// 加载界面
createWindow();
app.on('activate', () => {
console.log('激活的时候')
})
})
app.on('window-all-closed', () => {
console.log('所有窗口都关闭');
// 关闭应用
app.quit();
})
3. 生命周期
1. ready: app初始化完成
app.on('ready', () => {
const mainWin = new BrowserWindow({
width: 800,
height: 400
})
})
2. dom-ready: 一个窗口中的文本文件加载完成
webContents用于控制当前窗口的内容,每个窗口都有一个webContents。
mainWin.webContents.on('dom-ready', () => {
})
3. did-finsh-load: 导航完成时触发,dom-ready之后
mainWin.webContents.on('did-finsh-load', () => {
})
4. window-all-closed: 所有窗口都关闭时触发,如果没有监听会直接退出
app.on('window-all-closed', () => {
})
5. before-quit: 在关闭窗口之前触发
app.on(' before-quit', () => {
})
6. will-quit: 在窗口关闭并且应用退出时触发
app.on('will-quit', () => {
})
7. quit: 当所有窗口被关闭时触发
app.on('quit', () => {
})
8. close: 窗口关闭
mainWin.on('close', () => {
mainWin = null;
})
自动打包设置
"scripts": {
"start": "nodemon --watch main.js --exec npm run build",
"build": "electron ."
}
4. 窗口尺寸
const mainWin = new BrowserWindow({
...
})
mainWin.loadFile('index.html')
// 页面加载完成异步展示 避免页面空白
mainWin.on('ready-to-show', () => {
mainWin.show();
})
// x: number窗口位置
// y: number 窗口位置
// width: number 窗口宽度
// height: number 窗口高度
// shwo: boolean 默认是否显示窗体
// maxHeight: number 最大高度
// minHeight: number 最小高度
// maxWidth: number 最大宽度
// minWidth: number 最小宽度
// resizable: boolean 缩放设置
// title: string 窗口title, 如果html中设置了title标签则取title标签中的内容
// icon: string 窗口icon
// frame: boolean 是否展示标签页菜单栏标题栏
// transparent: boolean 窗体是否透明
// autoHideMenuBar: boolean 隐藏菜单
// webPreferences: {
// nodeIntegration: true, // 允许运行node环境
// enableRemoteModule: true // 允许使用remote模块
// }
5. 窗口通信
ctrl + r重载页面
ctrl + shift + i开启调试面板
渲染进程默认是无法使用require运行node的,可以通过开启webPreferences.nodeIntegration支持这个功能。开启之后可以使用remote找到主线程中的BrowserWindow,BrowserWindow只是给主线程使用的,不允许在渲染线程中调用。同时还需要开启webPreferences.enableRemoteModule功能。
const { remote } = require('electron');
const mainWin = new remote.BrowserWindow({
...
})
mainWin.loadFile('')
remote模块就是主线程和子线程通信的模块。
remote可以获取当前窗口对象
const mainWin = remote.getCurrentWindow;
// 关闭
mainWin.close();
// 最大化状态
mainWin.isMaximized()
// 最大化
mainWin.maximize();
// 回归初始状态
mainWin.restore();
// 最小化状态
mainWin.isMinimized()
// 最小化
mainWin.minmize();
窗口关闭提示
window.onbeforeunload = function() {
// 销毁 close会陷入死循环
mainWin.destroy();
// 禁止关闭
return false;
}
remote.BrowserWindow创建的窗口默认不具备父子关系,如果需要可以使用parent参数。这样就具备父子关系。不过有无父子关系使用区别基本不大。
remote.BrowserWindow({
parent: remote.getCurrentWindow(),
width: 200,
height: 200
})
如果希望子窗口出现父窗口不可操作可使用模态窗口,modal属性设置为true就可以了。
remote.BrowserWindow({
parent: remote.getCurrentWindow(),
width: 200,
height: 200,
modal: true
})
6. 进程间通信
1. 渲染进程向主进程发送消息,主进程接收消息。
渲染进程发送消息
const { ipcRenderer } = require('electron');
window.onload = function() {
// 采用异步消息向主线程发送消息
ipcRenderer.send('msg1', '参数字符串')
// 采用同步方式发送消息
ipcRenderer.sendSync('msg3', '同步消息')
}
主进程接收消息
const { ipcMain } = require('electron');
ipcMain.on('msg1', (e, data) => {
console.log(e, data);
})
ipcMain.on('msg3', (e, data) => {
console.log(e, data);
})
2. 主进程发送消息给渲染进程
主进程发送消息
const { ipcMain } = require('electron');
ipcMain.on('msg1', (e, data) => {
console.log(e, data);
// e.sender就是ipcMain
e.sender.send('msg2', '参数字符串')
// 主线程返回同步消息
e.returnValue = '发送同步消息'
})
渲染进程接收消息
const { ipcRenderer } = require('electron');
window.onload = function() {
// 采用异步消息向主线程发送消息
ipcRenderer.send('msg1', '参数字符串')
}
ipcRenderer.on('msg2', (ev, data) => {
console.log(e, data);
})
webContents.openDevTools() 开启开发者工具
mainWin.webContents.openDevTools()
7. dialog
dialog模块是electron内置模块,可以用于操作选择系统中的文件等。
8. shell
shell模块提供与桌面集成相关的功能。比如使用默认浏览器打开某地址。可以在所有线程中使用。
const { shell } = require('electron')
shell.openExternal('https://github.com')
9. iframe
electron不建议使用webview,建议使用iframe替代,或者最好不要用。
10. 消息通知
可以借助H5的消息通知功能来完成。
const notify = new window.Notification('标题', {
title: '标题',
body: '内容',
icon: './icon.png'
})
notify.onclick = function() {
console.log('点击了消息')
}
11. 注册快捷键
快捷点都是针对主进程的,快捷键需要在初始化完成之后绑定。销毁前清除。
const { globakShortcut } = require('electron')
app.on('ready', () => {
const ret = globakShortcut('ctrl + q', () => {
console.log('触发了快捷键')
})
if (!ret) {
console.log('注册失败')
}
// 是否注册
console.log(globakShortcut.isRegistered('ctrl + q'))
})
快捷键取消需要在will-quite生命周期中来做。
app.on('will-quit', () => {
// 清除单个
globakShortcut.unregister('ctrl + q')
// 清除所有注册
globakShortcut.unregisterAll()
})
12. 剪切板功能
剪切板也是不限制线程的模块,可以在任意线程中使用。
const { clipboard } = require('electron')
clipboard.writeText('写入剪切板');
// 读取剪切板
clipboard.readText;
将图片copy到剪切板
const { clipboard, nativeImage } = require('electron')
const image = nativeImage.createFromPath('./aaa.png')
clipboard.writeImage(image);
// 渲染到页面
img.src = image.toDataURL()