概述
Electron
核心组成分为三个部分,Chromium
,Node.js
,Native apis
。这三部分共同构成了Electron
架构。
Chromium
可以理解为浏览器,用于支持html
,css
,js
页面构成。Native apis
用于支持和操作系统的一些交互操作。Node.js
用于操作文件等。
Electron
可以看成一个框架,将chromium
及Node.js
整合到一起,它允许通过web
技术构建程序,可以通过Native apis
对操作系统进行一切操作。
Electron
内部存在不同的进程,一个是主进程,一个是渲染进程,当启动app
的时候首先会启动主进程,主进程完成之后就会创建Native UI
, 会创建一个或者多个 window
,用window
来呈现界面也就是web
界面。每个window
都可以看做一个渲染进程,各进程间相互独立,不同窗口数据可以通过rpc
或者ipc
通信
主进程可以看做是package.json
中main
属性对应的文件。一个应用只能有一个主进程,只要主进程可以进行GUI
的API
操作。主进程可以管理所有的web
界面和渲染进程。
安装electron
设置electron淘宝镜像
npm config set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/
mkdir my-electron-app && cd my-electron-app\
npm init
{\
"scripts": {\
"start": "electron ."\
}\
}
全局安装
npm install -g electron
创建index.html
<!DOCTYPE html>\
<html>\
<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'">\
<meta http-equiv="X-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>
创建mian.js-主进程
const { app, BrowserWindow } = require('electron')
const path = require('path')
/**app 控制着整个应用程序的事件生命周期, BrowserWindow创建和管理应用程序的窗口 */
function createWindow() {
const win = new BrowserWindow({
/**全屏 */
//fullscreen: false,
/**让桌面应用没有边框,这样菜单栏也会消失 */
//frame: false,
/**不允许用户改变窗口大小*/
// resizable: false,
/**设置窗口宽高 */
width: 800,
height: 600,
/**应用运行时的标题栏图标 */
//icon: iconPath,
/**最小宽度 */
//minWidth: 300,
/**最小高度 */
// minHeight: 500,
/**最大宽度*/
// maxWidth: 300,
/**最大高度 */
//maxHeight: 600,
/**进行对首选项的设置 */
webPreferences: {
preLoad: path.join(__dirname, 'preload.js'),
/**允许node环境运行 */
NodeIterator: true,
/**设置应用在后台正常运行*/
backgroundThrottling: false,
/**关闭警告信息*/
contextIsolation: false,
/**在主进程的窗口中加入enableRemoteModule: true参数才能够调用remote模块*/
enableRemoteModule: true
}
})
/**并且为你的应用加载index.html */
win.loadFile('index.html')
/**打开开发者工具 */
//win.webContents.openDevTools()
}
/**在electron中,只有app 模块的ready事件被触发才会创建浏览器窗口,我们可以通过app.whenReady()进行监听 */
app.whenReady().then(() => {
createWindow()
/**没有窗口打开就打开一个窗口 activate */
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
/**关闭所有窗口时退出应用(window-all-closed/app.quit) */
app.on('window-all-closed', function () {
if (ProcessingInstruction.platform !== 'darwin') app.quit()
})
创建preload.js
/**输出Electron的版本号和它的依赖项到你的web页面上。 */
window.addEventListener('DOMContentLoaded',() => {
const replaceText = (selector,text) =>{
const element = document.getElementById(selector)
if(element) element.innerText = text
}
for(const dependency of ['chrome','node','electron']){
replaceText(`${dependency}-version`,process.versions[dependency])
}
})
打包
npm install --save-dev @electron-forge/cli\
npx electron-forge import
分发
npm run make
- 安装过程中因为electron安装使用的是cnpm 安装,导致出现最后打包报错,建议安装使用统一的,如果慢先安装electron淘宝镜像
生命周期
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;
})
进程通信
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');
/**主线程 到 渲染线程 通过 webContents.send 来发送 --->ipcRenderer.on 来监听 */
/**
* webContents.send(channel, ...args)
channel String
...args any[]
*/
setTimeout(() => {
win.webContents.send('mainMsg', '我是主线程')
}, 1000);
ipcMain.on('msg1', (e, data) => {
console.log(e, data);
// e.sender就是ipcMain
e.sender.send('msg2', '参数字符串')
})
渲染进程接收消息
const { ipcRenderer } = require('electron');
window.onload = function() {
// 采用异步消息向主线程发送消息
ipcRenderer.send('msg1', '参数字符串')
}
ipcRenderer.on('mainMsg', (e, data) => {
console.log(e, data)
document.getElementById('receive').innerText = data
})
ipcRenderer.on('msg2', (ev, data) => {
console.log(e, data);
})
创建菜单
menu.js
const { Menu, BrowserWindow } = require('electron')
console.log(Menu, BrowserWindow,1)
const template = [
{
label: '菜单一',
/** submenu 代表下一级菜单 */
submenu: [
{
label: '子菜单一',
click: () => {
const newWin = new BrowserWindow({
width: 200,
height: 200
})
win.loadFile('./index2.html')
newWin.on('close', () => {
newWin = null
})
},
/** 添加快捷键 */
accelerator: 'ctrl+n'
},
{ label: '子菜单二' },
{ label: '子菜单三' },
{ label: '子菜单四' },
],
},
{
label: '菜单二',
/** 代表下一级菜单 */
submenu: [
{ label: '子菜单一' },
{ label: '子菜单二' },
{ label: '子菜单三' },
{ label: '子菜单四' },
],
}
]
console.log(222)
/** 3.从模板中创建菜单 */
const myMenu = Menu.buildFromTemplate(template)
/** 4.设置为应用程序菜单 */
Menu.setApplicationMenu(myMenu)
main.js
const { app, BrowserWindow } = require('electron')
const { ipcMain } = require('electron')
let win = null
require('./menu')
app.on('ready', function () {
// require('@electron/remote/main').initialize()
win = new BrowserWindow({
width: 800,
height: 600,
/**进行对首选项的设置 */
webPreferences: {
/**是否集成 Nodejs,把之前预加载的js去了,发现也可以运行*/
nodeIntegration: true,
contextIsolation: false,
/**在主进程的窗口中加入enableRemoteModule: true参数才能够调用remote模块*/
enableRemoteModule: true
}
})
win.webContents.openDevTools()
win.loadFile('./main.html')
win.on('close', () => {
win = null
})
node Api
在主线程创建窗口的时候 webPreferences
一定在加上 nodeIntegration: true
,contextIsolation: false
main.js
const { app, BrowserWindow } = require('electron')
const { ipcMain } = require('electron')
let win = null
app.on('ready', function () {
// require('@electron/remote/main').initialize()
win = new BrowserWindow({
width: 800,
height: 600,
/**进行对首选项的设置 */
webPreferences: {
/**是否集成 Nodejs,把之前预加载的js去了,发现也可以运行*/
nodeIntegration: true
}
})
win.webContents.openDevTools()
win.loadFile('./main.html')
win.on('close', () => {
win = null
})
index.js
const fs = require('fs')
const path = require('path')
const {log} = console
const showContent = document.getElementById('show_file_content')
function readFile(){
console.log('读取文件')
fs.readFile(path.join(__dirname,'a.txt'),(err,data)=>{
if(err){
throw new Error(err,'读取失败')
}
log(data)
showContent.innerText = data
})
}
const content = '今天学习electron'
function writeFile(){
console.log('写入文件')
fs.writeFile(path.join(__dirname,'b.txt'),content,'utf-8',(err,data)=>{
if(err){
throw new Error(err,'写入失败')
}
log('写入成功')
})
}
index.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>node获取api</title>
</head>
<body>
<button onclick="readFile()">读取文件</button>
<button onclick="writeFile()">写入文件</button>
<p id="show_file_content">页面内容</p>
<script src="./index.js"></script>
</body>
</html>
其他
弹窗
错误框
dialog.showErrorBox('警告', '操作有误');
复制代码
消息框
remote.dialog.showMessageBox({
type: 'info',
title: '提示信息',
message: '内容',
buttons: ['确定', '取消']
});
复制代码
打开文件
dialog.showOpenDialog(
{
properties: ['openFile']
},
function(date) {
console.log(date);
}
);
复制代码
保存文件
dialog.showSaveDialog(
{
title: 'save file',
filters: [
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
{ name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
{ name: 'Custom File Type', extensions: ['as'] },
{ name: 'All Files', extensions: ['*'] }
]
},
filename => {
console.log(filename);
}
);
进度条
mainWindow.setProgressBar(0.3)
mainWindow.setProgressBar(-1) // 删除进度条
消息通知
可以借助H5
的消息通知功能来完成。
const notify = new window.Notification('标题', {
title: '标题',
body: '内容',
icon: './icon.png'
})
notify.onclick = function() {
console.log('点击了消息')
}
系统托盘
设置系统托盘菜单和图标
const { Menu, Tray, BrowserWindow, app } = require('electron');
const path = require('path');
let iconTray = new Tray(path.join(__dirname, '../static/icon.png'));
let tpl = [
{
label: '设置',
click: function() {
console.log('setting');
}
},
{
label: '升级',
click: function() {
console.log('update');
}
},
{
label: '退出',
click: function() {
if (process.platform !== 'darwin') {
app.quit();
}
}
}
];
let trayTemplte = Menu.buildFromTemplate(tpl);
iconTray.setContextMenu(trayTemplte);
iconTray.setToolTip('地图');
快捷键
快捷点都是针对主进程的,快捷键需要在初始化完成之后绑定。销毁前清除。
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()
})
剪切板
剪切板也是不限制线程的模块,可以在任意线程中使用。
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()
调试
这块后面还需要学习如何调试