Electron学习
前置准备
设置镜像
npm config set registry https://registry.npmmirror.com/
npm config edit
然后加上配置
electron-mirror=https://cdn.npmmirror.com/binaries/electron/
配置环境
mkdir my-electron-app && cd my-electron-app
npm init -y
npm i --save-dev electron
# 使用全局安装的方式也可以,因为都是引用 electron 命令而已
npm i -g electron
# 使用 npx 命令检查是否安装成功
npx electron -v
package.json
配置,首先把这个 main
设置成自己想启动的主 JS 文件,然后启动脚本那里加上 chcp 65001
不然输出到控制台会有编码错误(通过命令 chcp 65001
就可以把当前命令行的输出转成 utf8 编码的)
{
"name": "studyElectron",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "chcp 65001 && electron ."
},
// ...
"dependencies": {
"electron": "^11.0.2"
},
"devDependencies": {
"electron": "^11.0.2"
}
}
主进程和渲染器进程
Electron 有两种进程:主进程、渲染进程(开始的 main.js
就是主进程)
整个生命周期只有一个主进程,但是可以有多个渲染进程
主进程:用于管理所有网页及其对应的渲染过程。
渲染进程:由于 Electron 使用 Chromium 来展示页面,所以 Chromium 的多进程结构也被充分利用。每个 Electron 的页面都在运行着自己的进程,这样的进程我们称之为渲染进程。
主进程使用 BrowserWindow
实例创建网页。每个 BrowserWindow
实例都在自己的渲染进程里运行着一个网页。当一个 BrowserWindow
实例被销毁后,相应的渲染进程也会被终止。
// 别设置这个 win 为 const,因为后面关闭时要设为 null
// 解除窗口对象的引用,如果没有这个引用,那么当该 JavaScript 对象被垃圾回收的
win.on('closed', ()=> {
win = null
})
主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的网页。
由于在网页里管理原生 GUI 资源是非常危险而且容易造成资源泄露,所以在网页面调用 GUI 相关的 APIs 是不被允许的。如果你想在网页里使用 GUI 操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作。
常见的事件
参考资料 Electron使用指南 - [03] Main Process API
app 生命周期事件:
在 Electron 完成初始化时被触发
app.whenReady().then(createWindow)
// or
app.on('ready',createWindow);所有窗口被关闭时触发
window-all-closed
事件开始关闭当前窗口触发
close
事件关闭了当前窗口触发
closed
事件应用程序开始,关闭窗口之前触发
before-quit
事件当窗口 失去焦点 时会触发
browser-window-blur
事件当窗口 获得焦点 时会触发
browser-window-focus
事件所有窗口都已经关闭,应用程序将退出触发
will-quit
事件所有应用程序退出时触发
quit
事件
secWindow = new BrowserWindow({
width: 400, height: 300,
webPreferences: { nodeIntegration: true },
})
mainWindow.on('focus', () => {
console.log('mainWindow focused')
})
secWindow.on('focus', () => {
console.log('secWindow focused')
})
app.on('browser-window-focus', () => {
console.log('App focused')
})
编写个 Hello World
编写一个启动主进程
// main.js
const { app, BrowserWindow } = require('electron')
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 这个是让 BrowserWindow 可以使用 NodeJS 的所有东西
nodeIntegration: true
}
})
win.loadFile('index.html')
// 可以直接访问一个网页:win.loadURL('https://alsritter.icu/')
win.webContents.openDevTools() // 打开开发人员工具(就是 Chrome 的那个)
}
// 在 electron app 第一次被初始化时创建了一个新的窗口。
app.whenReady().then(createWindow)
// 当窗口全部关闭时退出
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
/**
* 只有当应用程序激活后没有可见窗口时,才能创建新的浏览器窗口。
* 例如,在首次启动应用程序后,或重新启动已在运行的应用程序。
*/
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
渲染页
<!-- index.js -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello Electron</h1>
<button id="btn">读取文件里的文本</button>
<!-- 显示上面读取的文本 -->
<div id="mytext"></div>
<script src="render/index.js"></script>
</body>
</html>
渲染页调用的脚本使用 NodeJS 的文件模块读取本地文件
// render/index.js
const fs = require('fs').promises // 注意后面要加个 promises 才能用 Promises 语法
const path = require('path')
// 等待页面加载好了再执行这个回调函数
window.onload = () => {
// 获取 DOM
const btn = document.querySelector('#btn')
const mytext = document.querySelector('#mytext')
btn.onclick = () => {
fs.readFile(path.join(__dirname, '/assets/temp.txt'))
.then((result) => {
mytext.innerHTML = result
})
.catch((err) => {
console.log(err)
})
}
}
然后再在终端输入 npm start
就可以运行了(或者直接 electron .
)
Electron 开发自动刷新
参考资料 20分钟gulp快速入门
每次修改都去手动重启服务 或者 Ctrl + R 刷新应用,这样效率太慢了尤其是之后需要和 Vue 配合使用
方法一:使用 gulp
和 electron-connect
模块来监听修改并自动更新(这个方法只适合早期版本,现在已经无法使用了,这里只做记录)
注: Gulp 是基于 NodeJS 的一个自动化构建工具,开发者可以使用它构建自动化工作流程(前端集成开发环境)。例如:网页自动刷新,CSS 预处理,代码检测,图片压缩等功能,只需要简单的命令行就可以全部完成。使用它,可以简化工作,让你把重点放在功能的开发上,同时减少人为失误,提高开发的效率和质量。
# 全局安装这个 gulp-cli
npm install gulp-cli -g
# 本地安装这个 gulp
npm install gulp -D
# 安装 electron-connect
npm install electron-connect --save-dev
创建 gulpfile.js
内容如下
const gulp = require('gulp')
const electron = require('electron-connect').server.create()
// gulp.task('任务名,命名随意',回调函数);
gulp.task('watch:electron', function () {
electron.start();
gulp.watch(['./*.js'], electron.restart)
gulp.watch(['./src/*.{html,js,css}'], electron.reload)
})
然后通过 gulp watch:electron
启动
方法二:使用 electron-reloader
模块
npm install --save-dev electron-reloader
在程序入口文件 main.js
最下面加上
try {
require('electron-reloader')(module,{});
} catch (_) {}
electron-builder 打包
electron-builder 仓库 electron-builder 配置文档 参考资料 electron-builder打包见解
npm install electron-builder --save-dev
# 或全局安装
npm install electron-builder -g
示例 package.json
{
"name": "electron-quick-start",
"version": "1.0.0",
"description": "A minimal Electron application",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder"
},
// 只需配置 build 这里
"build": {
"productName":"xxxx",//项目名 这也是生成的exe文件的前缀名
"appId": "com.xxx.xxxxx",//包名
"copyright":"xxxx",//版权 信息
"directories": { // 输出文件夹
"output": "build"
},
"win": {
"icon": "build/favicon.ico",
"target": [
"nsis"
]
},
// NSIS(Nullsoft Scriptable Install System)是一个开源的Windows 系统下安装程序制作程序
"nsis": {
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerIcon": "./build/icons/aaa.ico",// 安装图标
"uninstallerIcon": "./build/icons/bbb.ico",//卸载图标
"installerHeaderIcon": "./build/icons/aaa.ico", // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "xxxx", // 图标名称
"include": "build/script/installer.nsh", // 包含的自定义nsis脚本 这个对于构建需求严格得安装过程相当有用。
"script" : "build/script/installer.nsh" // NSIS 脚本的路径,用于自定义安装程序。 默认为build/ installer.nsi
}
},
"repository": "https://github.com/electron/electron-quick-start",
"keywords": [
"Electron",
"quick",
"start",
"tutorial",
"demo"
],
"author": "GitHub",
"license": "CC0-1.0",
"devDependencies": {
"electron": "^8.2.5",
"electron-builder": "^22.6.1"
},
"dependencies": {
"bootstrap": "^4.5.0",
"electron-store": "^5.1.1",
"uuid": "^8.1.0"
}
}
打包命令
electron-builder build 构建命名 [default]
electron-builder install-app-deps 下载app依赖
electron-builder node-gyp-rebuild 重建自己的本机代码
electron-builder create-self-signed-cert 为Windows应用程序创建自签名代码签名证书
electron-builder start 使用electronic-webpack在开发模式下运行应用程序(须臾要electron-webpack模块支持)
Building(构建参数):
--mac, -m, -o, --macos Build for macOS, [array]
--linux, -l Build for Linux [array]
--win, -w, --windows Build for Windows [array]
--x64 Build for x64 (64位安装包) [boolean]
--ia32 Build for ia32(32位安装包) [boolean]
--armv7l Build for armv7l [boolean]
--arm64 Build for arm64 [boolean]
--dir Build unpacked dir. Useful to test. [boolean]
--prepackaged, --pd 预打包应用程序的路径(以可分发的格式打包)
--projectDir, --project 项目目录的路径。 默认为当前工作目录。
--config, -c 配置文件路径。 默认为`electron-builder.yml`(或`js`,或`js5`)
进程通信
ipcMain、ipcRenderer 通信
参考文档 Electron 文档 ipcMain 参考文档 Electron 文档 ipcRenderer 参考文档 Electron 文档 IpcMainEvent Object 参考文档 Electron 文档 IpcRendererEvent Object
ipcMain
用于从主进程到渲染进程的异步通信。ipcRenderer
用于从渲染器进程到主进程的异步通信。
// 语法
ipcMain.on(channel, listener)
// channel 就是自定义的事件名
// listener 是一个回调函数:接收两个参数 event IpcMainEvent 和 ...args any[]
// 这个同理,只是返回的 event 是 IpcRendererEvent
ipcRenderer.on(channel, listener)
其中的这个 IpcMainEvent
对象的主要参数:
frameId
:Integer
- 发送该消息的渲染进程框架的 ID(可能是 iframe)returnValue
:any
- 将其设置为要在同步消息中返回的值sender
:WebContents
- 返回发送消息的webContents
ports
:MessagePortMain[]
- 与此消息一起传输的消息端口列表reply
:Function
- 将 IPC 消息发送到渲染器框架的函数,该渲染器框架发送当前正在处理的原始消息。 使用 “reply” 方法回复发送的消息,以确保回复将转到正确的进程和框架。channel
:String
...args
:any[]
然后这个 IpcRendererEvent
对象的主要参数:
sender
:IpcRenderer
- IpcRenderer 实例是事件发起的源头senderId
:Integer
- 发送信息的webContents.id
,可以通过调用event.sender.sendTo(event.senderId, ...)
来回复此信息ports
:MessagePortMain[]
- 与此消息一起传输的消息端口列表
第一个例子:进程传递消息
// Renderer process
const {ipcRenderer} =require('electron');
const sendBtn = document.querySelector('#sendBtn');
// 发送信息给主进程
sendBtn.onclick = ()=> {
// 第一个参数是事件名,自己定义。
ipcRenderer.send('send-message-to-main','这是发送给主进程的消息');
}
// 渲染进程接收:
ipcRenderer.on('send-message-to-renderer', (event , data) => {
console.log('渲染进程收到的数据' , data);
})
// Main process
const {ipcMain} =require('electron');
ipcMain.on('send-message-to-renderer', (event , data) => {
console.log('主进程接受到的数据是' , data);
event.sender.send('send-message-to-renderer','这是主进程发给渲染进程的数据');
})
// ...
// 或者直接使用 mainWindow 去发送数据
mainWindow.webContents.send("send-message-to-renderer","这是主进程 mainWindow 发给渲染进程的数据");
第二个例子:渲染进程请求主进程打开浏览器
主进程代码:
const { app, BrowserWindow, shell, ipcMain } = require('electron');
...
ipcMain.on('open-url', (event, url) => {
shell.openExternal(url);
});
...
渲染进程代码:
const { shell, ipcRender } = require('electron');
const links = document.querySelectorAll('a[href]');
links.forEach(link => {
link.addEventListener('click', e => {
const url = link.getAttribute('href');
e.preventDefault();
ipcRender.send('open-url', url);
});
});
Remote 模块通信
参考资料 Electron remote 模块
Remote 模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径(渲染进程访问主进程)
Electron 中, 与 GUI 相关的模块(如 dialog
, menu
等)只存在于主进程,而不在渲染进程中。为了能从渲染进程中使用它们,需要用 ipc
模块来给主进程发送进程间消息。使用 remote
模块,可以调用主进程对象的方法,而无需显式地发送进程间消息。
下面是从渲染进程创建一个浏览器窗口的例子:
const remote = require('electron').remote;
// 注意:这里获取 BrowserWindow 是通过 remote 获取的
const BrowserWindow = remote.BrowserWindow;
// 也可以直接:
// const BrowserWindow = require('electron').remote.BrowserWindow;
var win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL('https://alsritter.icu/');
示例:页面中开启新页面
先在 main.js
上启用远程模块
// ...
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 这个是让 BrowserWindow 可以使用 NodeJS 的所有东西
nodeIntegration: true,
enableRemoteModule: true
}
})
win.loadFile('./src/demo02.html')
// ...
在 demo02.html
页面开启一个新的页面
<!-- ... -->
<body>
<h1>This Demo02 Page</h1>
<button id='btn'>打开新窗口</button>
<script src="../render/demo02.js"></script>
</body>
</html>
// ../render/demo02.js
const BrowserWindow = require('electron').remote.BrowserWindow
const btn = document.querySelector('#btn')
// 注意:原生的事件都是小写的所以别写成1 onLoad、onClick 这种
window.onload = () => {
btn.onclick = () => {
var win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('https://alsritter.icu/')
// 监听窗口关闭
win.on('closed', () => {
win = null
})
}
}
创建菜单并绑定事件
参考资料 Electron 文档 菜单项 参考资料 Electron menu 模块 参考资料 Electron MenuItem 模块
顶部菜单
编写一个菜单就是使用一个数组来标识有哪些项目,然后点击菜单项目能触发 点击事件
const { Menu, dialog } = require('electron')
const template = [
{
label: '工具',
submenu: [
{
label: '放大镜',
// 点击这个子菜单会触发这个回调
click: () => {
// 开启一个对话框
dialog.showMessageBox(
{
title: '工具', //信息提示框标题
message: '这是一个放大镜', //信息提示框内容
buttons: ['确定'], //下方显示的按钮
noLink: true, //win 下的样式
type: 'info' //图标类型
},
(index) => {
console.log(index)
}
)
},
// 设置快捷键
// 参考:https://www.electronjs.org/docs/api/accelerator
accelerator: 'CmdOrCtrl+M'
},
{ label: '计算器' }
]
},
{
label: '操作',
submenu: [
{ label: '保存' },
{
label: '检查回调参数',
// 回调函数: click(menuItem, browserWindow, event)
// 参考官方文档:https://www.electronjs.org/docs/api/menu-item
click: (item, browserWindow, event) => {
// 如果该 browserWindow 未打开会显示 undefined
if (browserWindow) {
console.log(`当前菜单标题是: ${item.label}`); // 注意控制台是 NodeJS 的那个
browserWindow.toggleDevTools(); // 打开调试工具
}
}
},
{ label: '新建' }
]
}
]
// 构建菜单模板
const m = Menu.buildFromTemplate(template)
// 使用这个菜单模板
Menu.setApplicationMenu(m)
然后再在 main.js
引入这个菜单模块
// ...
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 这个是让 BrowserWindow 可以使用 NodeJS 的所有东西
nodeIntegration: true,
enableRemoteModule: true
}
})
// 引入这个菜单模块
require('./main/menu')
// ...
右键菜单
参考资料 Electron 文档 菜单项
这个右键弹出菜单需要在 渲染进程 中监听右键事件
// 因为这是渲染进程,所以需要通过 remote 引入主进程的菜单模块
const {remote} = require('electron')
const rightTemplate = [{ label: '复制' }, { label: '粘贴' }]
const m = remote.Menu.buildFromTemplate(rightTemplate)
// 使用原生的监听 contextmenu 事件
window.addEventListener('contextmenu', (e) => {
// 先阻止默认行为
e.preventDefault()
// 在这个网页所属的 BrowserWindow 弹出菜单
// 参考文档:https://www.electronjs.org/docs/api/menu
m.popup(remote.getCurrentWindow())
})
创建全局快捷键
参考资料 Electron 文档 快捷键 参考资料 Electron 文档 系统快捷键
app.whenReady().then(() => {
// Register a 'CommandOrControl+X' shortcut listener.
const ret = globalShortcut.register('CommandOrControl+X', () => {
console.log('CommandOrControl+X is pressed')
})
if (!ret) {
console.log('registration failed')
}
// 检查快捷键是否注册成功
console.log(globalShortcut.isRegistered('CommandOrControl+X'))
})
app.on('will-quit', () => {
// 注销快捷键
globalShortcut.unregister('CommandOrControl+X')
// 注销所有快捷键
globalShortcut.unregisterAll()
})
对话框
选择文件对话框
参考文档 Electron 文档 对话框 参考资料 electron 打开选择文件框
// 语法 [] 表示可选
dialog.showOpenDialogSync([browserWindow, ]options)
// 例子
btn.onclick = () => {
function openDialog() {
const win = remote.getCurrentWindow()
// 加个这个焦点使其一直在最前面,使之必须选择文件后才能操作窗口
win.focus();
const result = remote.dialog.showOpenDialogSync(win, {
defaultPath: './',
properties: ['openFile']
})
console.log(result)
}
openDialog()
}
// 上面那个是同步的方法,这个 showOpenDialog 则是异步的
dialog.showOpenDialog([browserWindow, ]options)
// 例子
async function openDialog(){
const result = await remote.dialog.showOpenDialog({
properties: ['openFile'],
});
}
主要介绍这个 options
title
:String
(可选) - 对话框窗口的标题defaultPath
:String
(可选) - 对话框的默认展示路径buttonLabel
:String
(可选) - 「确认」按钮的自定义标签, 当为空时, 将使用默认标签。filters
:FileFilter[]
(可选){
filters: [
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
{ name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
{ name: 'Custom File Type', extensions: ['as'] },
{ name: 'All Files', extensions: ['*'] }
]
}properties
支持以下属性值openFile
- 允许选择文件openDirectory
- 允许选择文件夹multiSelections
- 允许多选。showHiddenFiles
- 显示对话框中的隐藏文件。createDirectory
macOS -允许你通过对话框的形式创建新的目录。promptToCreate
Windows - 如果输入的文件路径在对话框中不存在, 则提示创建。 这并不是真的在路径上创建一个文件,而是允许返回一些不存在的地址交由应用程序去创建。noResolveAliases
macOS - 禁用自动的别名路径(符号链接) 解析。 所选别名现在将会返回别名路径而非其目标路径。treatPackageAsDirectory
macOS -将包 (如 .app 文件夹) 视为目录而不是文件。dontAddToRecent
Windows - 不将打开的项目添加到 “最近文档” 列表中。
使用例:
dialog.showOpenDialogSync(mainWindow, {
title: '选择图片'
filters: [{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }],
properties: ['openFile']
})
两种实现方式
1、可以通过 ipc
通信,主进程实现打开文件对话框的操作,然后把选择的文件夹或者文件再次通过 ipc
通信发送的渲染进程(这里只做介绍,知道后就不再介绍这个方法了)
// in render.js
const {ipcRenderer} = require('electron');
function openDialog(){
ipcRenderer.send('openDialog');
}
ipcRenderer.on('selectedItem', (event, files)=>{
console.log(files);//输出选择的文件
})
//in main.js
const {ipcRenderer, dialog} = require('electron');
ipcRenderer.on('openDialog',(event)=>{
dialog.showOpenDialog({
}).then(result=>{
console.log(result); //输出结果
result.filePaths.length >0 && ipcRenderer.send(result.filePaths);
})
})
2、直接在渲染进程中,使用 remote
模块中的 dialog
模块打开。
const { remote } = require('electron');
async function openDialog(){
const result = await remote.dialog.showOpenDialog({
properties: ['openFile'],
});
}
例子:打开一张图片
<button id='btn'>选择图片</button>
<img id="image" style="width: 300px;"/>
const { remote } = require('electron')
const console = require('console') // 如果直接在渲染进程使用 console 则是浏览器那个控制台
const btn = document.querySelector('#btn')
const image = document.querySelector('#image')
// 这个 onload 是等待加载完再执行
window.onload = () => {
btn.onclick = () => {
function openDialog() {
const win = remote.getCurrentWindow()
win.focus()
remote.dialog
.showOpenDialog(win, {
filters: [{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }],
properties: ['openFile']
})
.then((result) => {
if (result) {
console.log(result.filePaths)
// 因为可以选择多个图片,所以返回的是一个数组
image.setAttribute('src', result.filePaths[0])
}
})
.catch((err) => {
console.log(err);
})
}
openDialog()
}
}
保存对话框
参考文档 Electron 文档 对话框
与上面的选择文件对话框大同小异,这里直接贴代码吧
const { remote } = require('electron')
const console = require('console')
const fs = require('fs') // 因为要保存文件,所以需要引入 fs 模块
const saveButton = document.querySelector('#saveButton')
saveButton.onclick = () => {
function saveDialog() {
const win = remote.getCurrentWindow()
win.focus()
remote.dialog
.showSaveDialog(win, {
title: '保存文件',
filters: [{ name: 'text', extensions: ['txt'] }],
defaultPath: '*.txt'
})
.then((result) => {
if (result) {
console.log(result.filePath)
fs.writeFileSync(result.filePath, '这里是保存到文件的内容')
}
})
.catch((err) => {
console.log(err)
})
}
saveDialog()
}
消息对话框
参考文档 Electron 文档 对话框 使用方法同上
// 语法
dialog.showMessageBox([browserWindow, ]options)
options
对象
type
String
(可选) - 可以为 "none", "info", "error", "question" 或者 "warning"。buttons
String[]
(optional) - 按钮的文本数组(可以有多个按钮)defaultId
Integer
(可选) - 在 message box 对话框打开的时候,设置默认选中的按钮,值为在 buttons 数组中的索引。title
String
(可选) - message box 的标题,一些平台不显示。message
String
- message box 的内容。detail
String
(可选) - 额外信息。checkboxLabel
String
(optional) - 如果提供,该消息框将包含具有给定标签的复选框。checkboxChecked
Boolean
(optional) - 复选框默认选项icon
NativeImage
(可选)cancelId
Integer
(可选) - 用于取消对话框的按钮的索引,例如 Esc 键。
返回值:Promise<Object>
response
Number - 选中的按钮的序号(对应上面的数组)checkboxChecked
Boolean - 如果设置了复选框标签,则表示复选框的选中状态。(没有则是 false)
注:在 Windows 上, "question" 与 "info" 显示相同的图标,除非使用了 "icon" 选项设置图标。 在 macOS 上, "warning" 和 "error" 显示相同的警告图标
const { remote } = require('electron')
const console = require('console')
const messageButton = document.querySelector('#messageButton')
messageButton.onclick = () => {
function messageDialog() {
const win = remote.getCurrentWindow()
win.focus()
remote.dialog
.showMessageBox(win, {
type: 'warning',
title: '这是警告弹窗',
buttons: ['按钮1', '按钮2', '按钮3'],
message: '这是弹窗的内容',
detail: '这是额外信息',
checkboxLabel: "不再显示这条消息"
})
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err)
})
}
messageDialog()
}
弹窗操作
外部浏览器打开链接
参考资料 Electron # 用默认浏览器打开链接的3种实现方式
默认在 Electron 点开 <a>
标签会在内部打开网页,这样效果不太好,所以需要设置它在外部浏览器打开这个网址,其核心原理就是通过 Electron 的 shell
模块中的 openExternal
方法来调用系统默认浏览器打开链接,如下有三种方法
1、在渲染进程中选择所有的 <a>
标签,覆盖 <a>
标签的默认点击方法,代码如下:
const { shell } = require('electron');
// 获取的是一个 Array
const links = document.querySelectorAll('a[href]');
links.forEach(link => {
link.addEventListener('click', e => {
const url = link.getAttribute('href');
e.preventDefault();
shell.openExternal(url);
});
});
这个方式只能接管自己可以维护的网页,不能更改第三方网页中链接的打开方式(例如:iframe 或该页面本身就是 url)
2、该方法与上一种方法类似,只不过换了一种角度来实现,这里打开连接并不在渲染进程中直接做,而是通过和主进程通信来告诉主进程调用系统浏览器打开链接,具体代码如下:
主进程代码:
const { app, BrowserWindow, shell, ipcMain } = require('electron');
...
ipcMain.on('open-url', (event, url) => {
shell.openExternal(url);
});
...
渲染进程代码:
const { shell, ipcRender } = require('electron');
const links = document.querySelectorAll('a[href]');
links.forEach(link => {
link.addEventListener('click', e => {
const url = link.getAttribute('href');
e.preventDefault();
ipcRender.send('open-url', url);
});
});
效果和第一种方法是一样的
3、通过在 主进程 中监听 webContents
的 new-window
事件来拦截所有的链接,具体代码:
const { shell, app } = require('electron');
app.on('web-contents-created', (e, webContents) => {
webContents.on('new-window', (event, url) => {
event.preventDefault();
shell.openExternal(url);
});
});
这种方式接管了所有链接的打开方式,优点就是可以处理 iframe 中或第三方网站链接的打开方式,缺点也很明显,不能单独控制那一类链接通过默认浏览器打开,哪一类链接通过 electron 直接打开。
嵌入网页
参考文档 Electron 文档 类: BrowserView
// 在主进程中.
const { BrowserView, BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 600 })
const view = new BrowserView()
// 将子窗口 view 嵌入到父窗口 win 中
win.setBrowserView(view)
// 注意:要在 win 设置了 BrowserView 之后才能对这个 view 进行设置
view.setBounds({ x: 0, y: 0, width: 300, height: 300 })
view.webContents.loadURL('https://electronjs.org')
打开子窗口
参考文档 MDN Window.open() 参考资料 菜鸟教程 Window open() 方法
// 语法
let windowObjectReference = window.open(strUrl, strWindowName, [strWindowFeatures]);
// strUrl === 要在新打开的窗口中加载的URL。
// strWindowName === 新窗口的名称。
// strWindowFeatures === 一个可选参数,列出新窗口的特征(大小,位置,滚动条等)
直接在渲染进程就能开启子窗口(这里使用的方法是原生的方法)
const btn = document.querySelector('#btn')
window.onload = () => {
btn.onclick = () => {
window.open('https://alsritter.icu/')
}
}
子窗口和上面的那个开启一个新窗口的区别是,上面的那种方式开启的窗口是独立的,即开启它的那个窗口关闭了也不影响它自己,而子窗口是父窗口关闭也跟着关闭
子窗口向父窗口传值
参考文档 MDN window.opener 参考文档 MDN Window 参考文档 MDN window.postMessage
子窗口向父窗口传值这里使用的也是原生的方法,主要就是这个 window.postMessage
方法:一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener
),然后在窗口上调用 targetWindow.postMessage()
方法分发一个 MessageEvent
消息。接收消息的窗口可以根据需要自由处理此事件。传递给 window.postMessage()
的参数(比如 message )
注:
opener
用于返回打开当前窗口的那个 窗口的引用,例如:在 window A 中打开了 window B,B.opener
返回 A.
// 语法
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow
:其他窗口的一个引用,比如 iframe 的contentWindow
属性或者是执行window.opener
返回的窗口对象message
:将要发送到其他 window 的数据targetOrigin
:通过窗口的 origin 属性来指定哪些窗口能接收到消息事件(必须满足同源策略才能发送)
子窗口的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>这是子窗口</h1>
<button onclick="popinfo()">向父窗口传递信息</button>
</body>
<!-- 计算机读代码的顺序是从上往下读的,html 文件中的顺序是 <head> → <body> → body 后方 -->
<!-- JavaScript 代码写在 <body> 下面的原因:
这时候整个网页已经加载完毕了,所以这里最适合放需要立即执行的命令-->
<script>
const popinfo = () =>{
window.opener.postMessage('这是子窗口传递的信息', '*')
}
</script>
</html>
父窗口
const btn = document.querySelector('#btn')
window.onload = () => {
btn.onclick = () => {
// 打开子窗口
window.open('../src/demo03.html')
}
// 添加一个监听,用来接收子窗口传递的值
window.addEventListener('message', (e)=>{
alert(e.data)
});
}
其它常用操作
剪贴板功能
例如点击一个按钮自动复制激活码的功能
<body>
<div id="code">这里是需要被复制的信息,例如一串很长的激活码</div>
<button id="copy">复制信息</button>
</body>
<script>
const {clipboard} = require('electron')
const copyBtn = document.querySelector('#copy')
const code = document.querySelector('#code')
copyBtn.onclick = () => {
clipboard.writeText(code.innerHTML)
alert('复制成功')
}
</script>
底部消息通知
这个也是原生的功能,就是 Win10 那个右下角的通知,下面演示一下基本用法,具体细节看参考文档
<body>
<button id="notifyBtn">底部消息</button>
</body>
<script>
const notifyBtn = document.querySelector('#notifyBtn')
notifyBtn.onclick = () => {
new window.Notification('这是标题', { body: '这是消息的主体内容' })
}
</script>