跳到主要内容

Electron学习

前置准备

参考资料

首先当然是 Electron 官网(文档很多地方是机翻...) 然后就是 w3cSchool 的 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 配合使用 方法一:使用 gulpelectron-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 对象的主要参数:

  • frameIdInteger - 发送该消息的渲染进程框架的 ID(可能是 iframe)
  • returnValueany - 将其设置为要在同步消息中返回的值
  • senderWebContents - 返回发送消息的 webContents
  • portsMessagePortMain[] - 与此消息一起传输的消息端口列表
  • replyFunction - 将 IPC 消息发送到渲染器框架的函数,该渲染器框架发送当前正在处理的原始消息。 使用 “reply” 方法回复发送的消息,以确保回复将转到正确的进程和框架。
  • channelString
  • ...argsany[]

然后这个 IpcRendererEvent 对象的主要参数:

  • senderIpcRenderer - IpcRenderer 实例是事件发起的源头
  • senderIdInteger - 发送信息的 webContents.id,可以通过调用 event.sender.sendTo(event.senderId, ...) 来回复此信息
  • portsMessagePortMain[] - 与此消息一起传输的消息端口列表

第一个例子:进程传递消息

// 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

  • titleString (可选) - 对话框窗口的标题
  • defaultPathString (可选) - 对话框的默认展示路径
  • buttonLabelString (可选) - 「确认」按钮的自定义标签, 当为空时, 将使用默认标签。
  • filtersFileFilter[] (可选)
    {
    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" 显示相同的警告图标

image.png

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、通过在 主进程 中监听 webContentsnew-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>

底部消息通知

参考文档 MDN 使用 Web Notifications

这个也是原生的功能,就是 Win10 那个右下角的通知,下面演示一下基本用法,具体细节看参考文档

<body>
<button id="notifyBtn">底部消息</button>
</body>

<script>
const notifyBtn = document.querySelector('#notifyBtn')

notifyBtn.onclick = () => {
new window.Notification('这是标题', { body: '这是消息的主体内容' })
}
</script>