electron基础
什么是Electron
第一个Electron
程序
npm i --save-dev electron
npm i nodemon
package.json
1 | { |
main.js
1 | const {app, BrowserWindow} = require('electron') |
index.html
1 |
|
renderer/index.js
1 | console.log(100) |
关闭所有的安全警告,不推荐
1 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' |
这个警告是关于CSP
的,可以在index.html
中配置CSP
策略,该策略允许导入本地资源
1 | <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'"> |
这个策略不一定涵盖所有的csp
警告,但至少我们可以知道忽略了什么警告
核心概念
Electron
主进程与渲染进程
主进程:启动项目时运行的main.js
就是我们所说的主进程,在主进程运行的脚本可以创建Web
页面的形式展示GUI
。主进程只有一个。
渲染进程:每个Electron
页面都在运行着自己的进程,这样的进程称之为渲染进程(基于Chromium
的多进程结构)
主进程使用BrowserWindow
创建实例,主进程销毁后,对应的渲染进程被终止,主进程与渲染进程通过IPC
方式(事件驱动)进行通信
早期Electron
中的渲染进程和主进程并没有隔离,渲染进程可以直接访问到主进程,有安全问题
1 | const win = new BrowserWindow({ |
此时可以在index.js
的渲染进程中,调用node
模块了
我们在桌面创建一个文本文件,里面内容是abc
renderer/index.js
1 | const fs = require('fs') |
我们关闭了隔离选项,用户可以在js
代码中直接操作node
的文件,这个就很危险了,现在不会这么做了
上述的配置项不要使用
那么应该怎么做呢?
就是接下来要学习的,关于主进程和渲染进程的方方面面
主进程事件生命周期
在Windows和Linux上,关闭所有窗口通常会完全退出一个应用程序。
为了实现这一点,你需要监听 app
模块的 'window-all-closed'
事件。如果用户不是在 macOS(darwin
) 上运行程序,则调用 app.quit()
。
关闭窗口
1 | app.on('window-all-closed', () => { |
当 Linux 和 Windows 应用在没有窗口打开时退出了,macOS 应用通常即使在没有打开任何窗口的情况下也继续运行,并且在没有窗口可用的情况下激活应用时会打开新的窗口。
为了实现这一特性,监听 app
模块的 activate
事件。如果没有任何浏览器窗口是打开的,则调用 createWindow()
方法。
因为窗口无法在 ready
事件前创建,你应当在你的应用初始化后仅监听 activate
事件。 通过在您现有的 whenReady()
回调中附上您的事件监听器来完成这个操作。
1 | app.whenReady().then(() => { |
注意:此时,您的窗口控件应功能齐全!
渲染进程如何使用Node
模块
在渲染进程开始之前,给了一个机会,去调用Node
通过webPreferences
配置项
1 | const path = require('path') |
任何去请求index.html
文件之前,先做一个预加载
将之前写文件的代码,放在preload.js
中
1 | const fs = require('fs') |
可以正确执行
preload.js
变量,想在index.html
里面用怎么办呢
透支一个概念,桥的概念,contextBridge
preload.js
1 | const {contextBridge} = require('electron') |
可以看到这是一个对象,以及它的作用域链情况
preload
中定义
1 | contextBridge.exposeInMainWorld('myApi', { |
renderer/index.js
中使用
1 | console.log(window.myApi) |
通过contextBridge
可以将Node
中的模块及其他变量,挂载到渲染进程的window
对象上
主进程与渲染进程通信
主进程中注册使用ipcMain.handle
监听一个channel
main.js
1 | // 主进程中注册好事件 |
预加载器中使用ipcMain.renderer
向指定channel
发送消息,并通过contextBridge
挂载到渲染进程的window
对象上
preload.js
1 | const handleSend = (arg) => { |
渲染进程使用暴露的方法,与主进程通信
1 | document.querySelector('#btn').addEventListener('click', () => { |
主进程
Electron API
有两种
Main Process
(主进程)Renderer Process
(渲染进程)
App
事件
before-quit
在应用程序开始关闭之前触发
1 | app.on('before-quit', () => { |
browser-window-blur
在
browserWindow
失去焦点时触发
1 | app.on('browser-window-blur', (e) => { |
browser-window-focus
在
browserWindow
获得焦点时触发
1 | app.on('browser-window-focus', (e) => { |
方法
app.quit()
1 | app.on('browser-window-blur', (e) => { |
app.getPath(name)
1 | app.whenReady().then(() => { |
结果
1 | C:\Users\Administrator\Desktop |
BrowserWindow
electron.BrowserWindow
:创建和控制浏览器窗口
实例方法
win.loadURL(url[, options])
和loadFile
互斥
1 | const createWindow = () => { |
优雅的显示窗口
使用
ready-to-show
事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const createWindow = () => {
const win = new BrowserWindow({
width: 1000,
height: 800,
show: false, // 一开始不显示
webPreferences: {
nodeIntegration: true,
preload: path.resolve(__dirname, './preload.js')
},
})
win.loadURL('https://www.mindcons.cn')
win.webContents.openDevTools()
win.on('ready-to-show', () => { // 准备完资源后再显示,看具体情况使用
win.show()
})
}设置窗口背景颜色,该颜色并不是通过给标签元素添加背景颜色实现的,而是设置的窗口背景颜色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const createWindow = () => {
const win = new BrowserWindow({
width: 1000,
height: 800,
show: false,
backgroundColor: 'purple',
webPreferences: {
nodeIntegration: true,
preload: path.resolve(__dirname, './preload.js')
},
})
win.loadURL('https://www.mindcons.cn')
win.webContents.openDevTools()
win.on('ready-to-show', () => {
win.show()
})
}
父子窗口
窗口定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27const createWindow = () => {
const win = new BrowserWindow({
width: 1000,
height: 800,
show: false,
backgroundColor: 'purple',
webPreferences: {
nodeIntegration: true,
preload: path.resolve(__dirname, './preload.js')
},
})
win.loadFile('index.html')
win.webContents.openDevTools()
win.on('ready-to-show', () => {
win.show()
})
const win2 = new BrowserWindow({
width: 600,
height: 400
})
win2.loadURL('https://www.baidu.com')
}窗口关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29const createWindow = () => {
const win = new BrowserWindow({
width: 1000,
height: 800,
show: false,
backgroundColor: 'purple',
webPreferences: {
nodeIntegration: true,
preload: path.resolve(__dirname, './preload.js')
},
})
win.loadFile('index.html')
win.webContents.openDevTools()
win.on('ready-to-show', () => {
win.show()
})
const win2 = new BrowserWindow({
width: 600,
height: 400,
parent: win, // 指定父级关联关系,父子窗口都可点击
modal: true, // 指定为模态窗口,只能操作win2窗口
})
win2.loadURL('https://www.baidu.com')
}
无边框窗口
1 | const win = new BrowserWindow({ |
配合CSS
实现拖拽
在此之前我们用nodemon
监听html
等文件的变化
1 | "start": "nodemon --exec electron . --watch ./ --ext .js, .html, .css, .vue" |
注意由于meta
配置的csp
策略,不能写内部样式。另外link
标签必须指定rel
属性,否则引入失效,估计和渲染引擎有关
1 |
|
style.css
1 | html { |
显示红绿灯(最小化、最大化、关闭)
1 | const win = new BrowserWindow({ |
实测下,windows
并没有出现