前言
Electron 可以让你使用纯 JavaScript 调用丰富的原生(操作系统) APIs 来创造桌面应用。 你可以把它看作一个 Node. js 的变体,它专注于桌面应用而不是 Web 服务器端。
这不意味着 Electron 是某个图形用户界面(GUI)库的 JavaScript 版本。 相反,Electron 使用 web 页面作为它的 GUI,所以你能把它看作成一个被 JavaScript 控制的,精简版的 Chromium 浏览器。
起步
代码语言:javascript复制git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
npm install
npm start
创建窗口
代码语言:javascript复制const { app, BrowserWindow } = require('electron')
// 保持对window对象的全局引用,如果不这么做的话,当JavaScript对象被
// 垃圾回收的时候,window对象将会自动的关闭
let win
function createWindow () {
// 创建浏览器窗口。
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// 加载index.html文件
win.loadFile('index.html')
// 打开开发者工具
win.webContents.openDevTools()
// 当 window 被关闭,这个事件会被触发。
win.on('closed', () => {
// 取消引用 window 对象,如果你的应用支持多窗口的话,
// 通常会把多个 window 对象存放在一个数组里面,
// 与此同时,你应该删除相应的元素。
win = null
})
}
// Electron 会在初始化后并准备
// 创建浏览器窗口时,调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow)
// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
// 在 macOS 上,除非用户用 Cmd Q 确定地退出,
// 否则绝大部分应用及其菜单栏会保持激活。
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 在macOS上,当单击dock图标并且没有其他窗口打开时,
// 通常在应用程序中重新创建一个窗口。
if (win === null) {
createWindow()
}
})
// 在这个文件中,你可以续写应用剩下主进程代码。
// 也可以拆分成几个文件,然后用 require 导入。
主进程和渲染进程
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)异步
在主进程中
代码语言:javascript复制const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.reply('asynchronous-reply', 'pong')
})
在渲染器进程 (网页) 中
代码语言:javascript复制const { ipcRenderer } = require('electron')
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
2)同步
在主进程中
代码语言:javascript复制const { ipcMain } = require('electron')
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})
在渲染器进程 (网页) 中
代码语言:javascript复制const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
主进程=>渲染进程
在主进程中
代码语言:javascript复制const { ipcMain } = require('electron')
let rtmpWindow = new BrowserWindow({
width: 400,
height: 600,
frame: false,
hasShadow: false,
show: false,
title: "",
webPreferences: {
nodeIntegration: true
}
});
rtmpWindow.loadFile("views/rtmp.html");
rtmpWindow.webContents.openDevTools()
rtmpWindow.webContents.on('did-finish-load', () => {
//下面的这行代码要在上面的BrowserWindow加载完成之后调用
rtmpWindow.webContents.send('asynchronous-msg',"test");
})
在渲染器进程 (网页) 中
代码语言:javascript复制const { ipcRenderer } = require('electron')
ipcRenderer.on('asynchronous-msg', (event, arg) => {
console.log(arg) // prints "pong"
})
渲染进程=>渲染进程
使用全局共享属性
使用全局共享属性或者用 Storage API( localStorage
,sessionStorage
或者 IndexedDB
)。
但不具备事件机制,没有实质的通信功能。
代码语言:javascript复制// 主进程中在global上自定义对象
global.saveDefault= {
token: 'default value',
name: 'default value',
password: 'default value',
}
// 在登录页 In page 1
require('electron').remote.getGlobal('saveDefault').token= 'token'
require('electron').remote.getGlobal('saveDefault').name= 'name'
// 在主页 In page 2 就可以获取到
console.log(require('electron').remote.getGlobal('saveDefault').name)
console.log(require('electron').remote.getGlobal('saveDefault').token)
console.log(require('electron').remote.getGlobal('saveDefault').password)
利用主进程做消息中转
代码语言:javascript复制// 渲染进程1
ipcRenderer.send('ping-event', 'ping')
// 在主进程中
ipcMain.on('ping-event', (event, arg) => {
yourWindow.webContents.send('pong-event', 'something');
}
// 渲染进程2
ipcRenderer.on('pong-event', (event, arg) => {
// do something
}
)
使用 ipcRenderer.sendTo()
代码语言:javascript复制// 渲染进程
ipcRenderer.sendTo(webContentsId, channel, [, arg1][, arg2][, ...])
ipcRenderer.sendTo(winId, 'ping', 'someThing')
利用 remote 接口直接获取渲染进程发送消息
代码语言:javascript复制// 渲染进程
// 获取窗口的id
remote.BrowserWindow.fromId(winId).webContents.send('ping', 'someThing');
获取进程id的方法
第一种: 通过 global 设置和获取
代码语言:javascript复制// 主进程中在global上自定义对象
global.winIds= {
win1Id : win1.id,
win2Id : win2.id
}
// 主进程获取
global.winIds["win1Id"];
// 渲染进程获取
require('electron').remote.getGlobal('winIds').win1Id
第二种是: 主进程创建事件,发送信息(不推荐)
代码语言:javascript复制// 主进程中
win1.webContents.send('distributeIds',{
win2Id : win2.id
});
win2.webContents.send('distributeIds',{
win1Id : win1.id
});
页面数据共享
在两个网页(渲染进程)间共享数据最简单的方法是使用浏览器中已经实现的 HTML5 API。 其中比较好的方案是用 Storage API( localStorage
,sessionStorage
或者 IndexedDB
)。
你还可以用 Electron
内的 IPC 机制实现。将数据存在主进程的某个全局变量中,然后在多个渲染进程中使用 remote
模块来访问它。
在主进程中
代码语言:javascript复制global.sharedObject = {
someProperty: 'default value'
}
在第一个页面中
代码语言:javascript复制require('electron').remote.getGlobal('sharedObject').someProperty = 'new value';
在第二个页面中
代码语言:javascript复制console.log(require('electron').remote.getGlobal('sharedObject').someProperty);
常见问题
jQuery/RequireJS/Meteor/AngularJS 的问题
jQuery 等新版本的框架,在 Electron 中使用普通的引入的办法会引发异常,原因是 Electron 默认启用了 Node.js 的 require 模块,而这些框架为了支持 commondJS 标准,当 Window 中存在 require 时,会启用模块引入的方式。
分别有以下几种解决方案:
方式一:
使用 Electron 官方论坛提供的方法,改变require的写法。下面的代码各个框架通用:
代码语言:javascript复制//在引入框架之前先输入下面的代码
<script>
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
</script>
方式二:
禁用Node.js的require模块化引入(如果你不想使用 Node.js 模块):
代码语言:javascript复制// In the main process.
let win = new BrowserWindow({
webPreferences: {
nodeIntegration: false
}
});
方式三:
为使 web 项目正常浏览,在引入 jquery 后进行判断:
代码语言:javascript复制//置于引入 jQuery 之后
<script>
if (typeof module === 'object') {window.jQuery = window.$ = module.exports;};
</script>
关于页面跳转 的问题
在接收到命令后创建下一个窗口(创建窗口需要时间,期间可能出现空白):
代码语言:javascript复制//在main.js中::
const ipc = require('electron').ipcMain;
//进行监控,如果有new-window 发送过来,则重新创建一个窗口,文件是list.html
ipc.on('new-window', function () {
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, '/views/list.html'),
protocol: 'file:',
slashes: true
}))
});
关于无边框窗口 的问题
为了使窗口无边框,使得在某些时候让项目看起来更美观,所以在创建窗口的时候通过设置 frame 属性的值为 false 来创建无边框窗口。
但是无边框窗口会产生无法移动的问题,对于这个问题我们可以在渲染进程中通过编辑 css 文件来解决。
设置 -webkit-app-region: no-drag
禁止拖拽:
body {
-webkit-app-region: no-drag
}
需要拖拽的地方设置:
代码语言:javascript复制section {
-webkit-app-region: drag;
}