前言
Electron 可以让你使用纯 JavaScript 调用丰富的原生(操作系统) APIs 来创造桌面应用。 你可以把它看作一个 Node. js 的变体,它专注于桌面应用而不是 Web 服务器端。
这不意味着 Electron 是某个图形用户界面(GUI)库的 JavaScript 版本。 相反,Electron 使用 web 页面作为它的 GUI,所以你能把它看作成一个被 JavaScript 控制的,精简版的 Chromium 浏览器。
起步
1 | git clone https://gitee.com/psvmc/electron-quick-start.git |
创建窗口
1 | const { app, BrowserWindow } = require('electron') |
常用路径
程序安装目录
1 | const {app} = window.require("electron").remote; |
其中homeDir的路径为:
- 开发环境 D:\Project\Electron\school_live_client\node_modules\electron\dist\
- 打包环境 安装目录中exe的同级目录
EXE路径
可执行文件路径
1 | app.getPath("exe") |
结果
app.getPath(“exe”): D:\Project\Electron\school_live_client\node_modules\electron\dist\electron.exe
注意:
这里是开发时打印的路径,发布后是不一样的,是
安装后的文件夹路径+exe文件名称
用户相关的路径
home
用户的 home 文件夹(主目录)documents
用户文档目录的路径downloads
用户下载目录的路径music
用户音乐目录的路径pictures
用户图片目录的路径videos
用户视频目录的路径
1 | app.getPath("home"); |
结果
app.getPath(“home”): C:\Users\18351
app.getPath(“documents”): C:\Users\18351\Documents
app.getPath(“downloads”): C:\Users\18351\Downloads
app.getPath(“music”): C:\Users\18351\Music
app.getPath(“pictures”): C:\Users\18351\Pictures
app.getPath(“videos”): C:\Users\18351\Videos
注意:
这些路径中包含用户名,如果计算机的用户名是中文的话,路径中就会包含中文,有些第三方的库使用的路径可能不支持中文,一定要注意!!!
桌面路径
1 | app.getPath("desktop") |
结果
app.getPath(“desktop”): C:\Users\18351\Desktop
注意:
桌面路径中包含用户名,如果计算机的用户名是中文的话,路径中就会包含中文,有些第三方的库使用的路径可能不支持中文,一定要注意!!!
AppData下
appData
每个用户的应用程序数据目录,默认情况下指向%APPDATA%
Windows 中$XDG_CONFIG_HOME
or~/.config
Linux 中~/Library/Application Support
macOS 中
userData
储存你应用程序设置文件的文件夹,默认是appData
文件夹附加应用的名称logs
应用程序的日志文件夹cache
缓存路径temp
临时文件夹
代码1
2
3
4
5app.getPath("appData");
app.getPath("userData");
app.getPath("logs");
app.getPath("cache");
app.getPath("temp");
结果
app.getPath(“appData”): C:\Users\18351\AppData\Roaming
app.getPath(“userData”): C:\Users\18351\AppData\Roaming\应用名称
app.getPath(“logs”): C:\Users\18351\AppData\Roaming\应用名称\Electron\logs
app.getPath(“cache”): C:\Users\18351\AppData\Roaming
app.getPath(“temp”): C:\Users\18351\AppData\Local\Temp
注意:
注意
用户名
或应用名
为中文的时候,logs
和userData
中会包含中文,有些第三方的库使用的路径可能不支持中文,一定要注意!!!
程序根目录
不建议使用
1 | const {app} = window.require("electron").remote; |
结果
app.getAppPath(): D:\Project\Electron\school_live_client
注意:
这个在开发时时源代码所在路径。
这是应用的安装路径和应用名称没有必然关系,一定要注意。
打包后的路径为
假如程序的安装路径为D:\MyApp
D:\MyApp\resources\app.asar
这个路径是在压缩文件内不建议使用。
不建议使用的路径
这两个路径会报错
recent
用户最近文件的目录 (仅限 Windows)。crashDumps
崩溃转储文件存储的目录。
返回 String
- 一个与 name
相关的特殊目录或文件的路径。 失败会抛出一个Error
。
自动重新加载页面
1 | npm install --save-dev electron-reloader |
添加下面代码到main.js的最下面
1 | const {app} = require("electron"); |
前端页面自动刷新
添加依赖
1 | npm install --save-dev electron-reload |
添加下面代码到main.js的最下面
1 | const {app} = require("electron"); |
这个插件跟上面的区别在于我们可以指定自动刷新所监听的文件夹
主进程和渲染进程
Electron 运行 package.json
的 main.js
脚本的进程被称为主进程。
在主进程中运行的脚本通过创建web页面来展示用户界面。
一个 Electron 应用总是有且只有一个主进程。
每个 Electron 中的 web 页面运行在它自己的渲染进程中。
主进程管理所有的web页面和它们对应的渲染进程。 每个渲染进程都是独立的,它只关心它所运行的 web 页面。
Electron同时在主进程和渲染进程中对Node.js 暴露了所有的接口。
主进程和渲染进程模块
两种进程都可用的模块
- clipboard 在系统剪贴板上执行复制和粘贴操作。
- crashReporter 将崩溃日志提交给远程服务器。
- nativeImage 使用 PNG 或 JPG 文件创建托盘、dock和应用程序图标。
- shell 使用默认应用程序管理文件和 url。
Main Process 模块
- app 控制你的应用程序的事件生命周期。
- autoUpdater 使应用程序能够自动更新
- BrowserView 创建和控制视图,相当于Android中的Fragment。
- BrowserWindow 创建和控制浏览器窗口,相当于Android中的Activity。
- contentTracing 从Chromium的内容模块收集跟踪数据,以查找性能瓶颈和缓慢的操作。
- dialog 显示用于打开和保存文件、警报等的本机系统对话框。
- globalShortcut 在应用程序没有键盘焦点时,监听键盘事件。
- inAppPurchase Mac App Store中的应用内购买。
- ipcMain 从主进程到渲染进程的异步通信。
- Menu 创建原生应用菜单和上下文菜单。
- MenuItem 添加菜单项到应用程序菜单和上下文菜单中。
- net 使用Chromium的原生网络库发出HTTP / HTTPS请求。
- netLog 网络请求日志。
- powerMonitor 监视电源状态的改变。
- powerSaveBlocker 阻止系统进入低功耗 (休眠) 模式。
- protocol 注册自定义协议并拦截基于现有协议的请求。
- screen 检索有关屏幕大小、显示器、光标位置等的信息。
- session 管理浏览器会话、cookie、缓存、代理设置等。
- systemPreferences 获取system preferences。
- 触控板 为原生macOS应用创建TouchBar布局。
- Tray 添加图标和上下文菜单到系统通知区。
- webContents 渲染以及控制 web 页面。
Renderer Process 模块
- desktopCapturer 从桌面上捕获音频和视频的媒体源信息。
- ipcRenderer 从渲染器进程到主进程的异步通信。
- remote 在渲染进程中使用主进程模块。
- webFrame 自定义渲染当前网页。
进程间通讯
渲染进程=>主进程=>渲染进程
异步
在渲染器进程 (网页) 中
1 | const { ipcRenderer } = require("electron"); |
在主进程中
1 | const { ipcMain } = require("electron") |
同步
在渲染器进程 (网页) 中
1 | const { ipcRenderer } = require('electron') |
在主进程中
1 | const { ipcMain } = require('electron') |
主进程=>渲染进程
在主进程中
1 | const { ipcMain } = require('electron') |
在渲染器进程 (网页) 中
1 | const { ipcRenderer } = require('electron') |
渲染进程=>渲染进程
使用全局共享属性
使用全局共享属性或者用 Storage API( localStorage
,sessionStorage
或者 IndexedDB
)。
但不具备事件机制,没有实质的通信功能。
1 | // 主进程中在global上自定义对象 |
利用主进程做消息中转
1 | // 渲染进程1 |
使用 ipcRenderer.sendTo()
1 | // 渲染进程 |
利用 remote 接口直接获取渲染进程发送消息
1 | // 渲染进程 |
获取进程id的方法
第一种: 通过 global 设置和获取
1 | // 主进程中在global上自定义对象 |
第二种是: 主进程创建事件,发送信息(不推荐)
1 | // 主进程中 |
BrowserView与BrowserWindow
先说结论如果你的窗口内只加载一个页面就不要用BrowserView了,因为它不会带来任何好处。
本来我以为使用BrowserWindow创建窗口每一个窗口都会创建一个Electron进程,如果页面使用Node也会同事创建一个Node进程,好浪费资源啊,那么是不是使用BrowserView就会减少呢,毕竟它是依存于BrowserWindow而存在的(也就是说BrowserWindow隐藏,里面的BrowserView都会隐藏),但实际上压根没有减少资源的消耗,还增大了代码复杂度,还有很多坑。
BrowserView的劣势
必须配置下方代码才能使用Node环境,并不是只配置BrowserWindow就可以。
1
2
3webPreferences: {
nodeIntegration: true
}必须先把BrowserView添加到BrowserWindow后再加载页面,否则页面添加的事件不生效。
1
2classMainWin.addBrowserView(blackboardView);
blackboardView.webContents.loadURL(blackboardURL);BrowserView初始化设置窗口大小无效,必须初始化后调用下面方法设置窗口大小。
1
2
3
4
5
6pptView.setBounds({
width: winW,
height: winH,
x: 0,
y: 0
});可以添加多个子窗口,但是无法设置子窗口的显示隐藏。
添加的子窗口也要新开进程,并没有节省资源。
是实验性方法,后期可能被删除。
添加的窗口可以是多标签形式,但是支持Mac。
BrowserView什么时候用
- 想在一个窗口同时显示多个页面。
- 只用适配Mac,并想实现多标签页。
总而言之强烈不推荐使用BrowserView。
页面数据共享
渲染进程之间
在两个网页(渲染进程)间共享数据最简单的方法是使用浏览器中已经实现的 HTML5 API。 其中比较好的方案是用 Storage API( localStorage
,sessionStorage
或者 IndexedDB
)。
所有进程间
但是如果要想在主进程和渲染进程之间共享数据,就不能用上面所说的方式了。
可以用 IPC 机制
或global.sharedObject
IPC
用 Electron
内的 IPC 机制实现。
global.sharedObject
将数据存在主进程的某个全局变量中,然后在多个渲染进程中使用 remote
模块来访问它。
在主进程中
1 | global.sharedObject = { |
在第一个页面中
1 | const remote = window.require("electron").remote; |
在第二个页面中
1 | const remote = window.require("electron").remote; |
主进程
1 | global.sharedObject.username = "123"; |
常见问题
jQuery/RequireJS/Meteor/AngularJS 的问题
jQuery 等新版本的框架,在 Electron 中使用普通的引入的办法会引发异常,原因是 Electron 默认启用了 Node.js 的 require 模块,而这些框架为了支持 commondJS 标准,当 Window 中存在 require 时,会启用模块引入的方式。
分别有以下几种解决方案:
方式一:
使用 Electron 官方论坛提供的方法,改变require的写法。下面的代码各个框架通用:
1 | //在引入框架之前先输入下面的代码 |
方式二:
禁用Node.js的require模块化引入(如果你不想使用 Node.js 模块):
1 | // In the main process. |
方式三:
为使 web 项目正常浏览,在引入 jquery 后进行判断:
1 | //置于引入 jQuery 之后 |
关于页面跳转 的问题
在接收到命令后创建下一个窗口(创建窗口需要时间,期间可能出现空白):
1 | //在main.js中:: |
关于无边框窗口 的问题
为了使窗口无边框,使得在某些时候让项目看起来更美观,所以在创建窗口的时候通过设置 frame 属性的值为 false 来创建无边框窗口。
但是无边框窗口会产生无法移动的问题,对于这个问题我们可以在渲染进程中通过编辑 css 文件来解决。
设置 -webkit-app-region: no-drag
禁止拖拽:
1 | body { |
需要拖拽的地方设置:
1 | section { |
electron报错:fs.existsSync is not a function
使用vue-cli-plugin-electron-builder创建electron项目,在渲染进程中require(‘electron’)会报错:
fs.existsSync is not a function
解决办法:
需要修改两个地方
渲染进程中引入模块,改成使用window.require
引入
1 | const electron = window.require('electron'); |
主进程中,加上nodeIntegration: true
1 | win = new BrowserWindow({ |
图标显示不完整
使用下面软件制作ICO图标
链接:https://pan.baidu.com/s/1FAYikTjto2SbobuwxNFkKg
提取码:641i
图标建议尺寸256x256
获取窗口句柄
1 | const win = window.require("electron").remote.getCurrentWindow(); |