文档地址: https://www.electronjs.org/
process.platform:获取平台
Ctrl + Shift + i :调出调试面板
技术架构
- Chromium:支持最新特性的浏览器
- Node.js:JavaScript 运行时,可实现文件读写等
- Native APIs:提供统一的原生界面能力
Electron 工作流程
主进程
- 可以看做是 package.json 中 main 属性对应的文件
- 一个应用只会有一个主进程
- 只有主进程可以进行 GUI 的 API 操作
渲染进程
- Windows 中展示的界面通过渲染进程表现
- 一个应用可以有多个渲染进程
环境搭建
# 克隆示列项目的仓库
git clone https://github.com/electron/electron-quick-start
# 进入这个仓库
cd electron-quick-start
# 安装依赖并运行
npm install && npm start
Electron 生命周期
- ready:app 初始化完成
- dom-ready:一个窗口中的文本加载完成
- did-finish-load:导航完成时触发
- window-all-closed:所有窗口都被关闭时触发
- before-quit:在关闭窗口之前触发
- will-quit:在窗口关闭并且应用退出时触发
- quit:当所有窗口被关闭时触发
- close:当窗口关闭时触发,此时应删除窗口引用
窗口尺寸
const { app, BrowserWindow } = require('electron')
// 将创建窗口独立成一个函数
function createWindow() {
let mainWin = new BrowserWindow({
x: 100,
y: 100, // 设置窗口显示的位置,相对于当前屏幕的左上角
show: false, // 默认情况下创建一个窗口对象之后就会显示,设置为false 就不会显示了
width: 800,
height: 400,
maxHeight: 600,
maxWidth: 1000,
minHeight: 200,
minWidth: 300, // 可以通过 min max 来设置当前应用窗口的最大和最小尺寸
resizable: false // 是否允许缩放应用的窗口大小
})
mainWin.loadFile('index.html')
mainWin.on('ready-to-show', () => {
mainWin.show()
})
mainWin.on('close', () => {
console.log('mainWin is closed')
mainWin = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
console.log('all window is closed')
app.quit()
})
窗口标题
let mainWin = new BrowserWindow({
show: true,
width: 800,
height: 600,
frame: true, // 用于自定义 menu ,设置为 false 可以将默认的菜单栏隐藏
// transparent: true,
autoHideMenuBar: true,
icon: 'lg.ico', // 设置一个图片路径,可以自定义当前应用的显示图标
title: "拉勾教育", // 自定义当前应用的显示标题
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
})
自定义窗口实现
let mainWin = new BrowserWindow({
frame: false,
width: 800,
height: 400,
webPreferences: { // 用于控制窗口加载的网页是否集成 node.js 环境
nodeIntegration: true,
enableRemoteModule: true
}
})
const { remote } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
// 利用 remote 可以获取当前窗口对象
let mainWin = remote.getCurrentWindow()
// 获取元素添加点击操作的监听
let aBtn = document.getElementsByClassName('windowTool')[0].getElementsByTagName('div')
aBtn[0].addEventListener('click', () => {
// 当前事件发生后说明需要关闭窗口
mainWin.close()
})
aBtn[1].addEventListener('click', () => {
// 这里需要执行的最大化操作
console.log(mainWin.isMaximized())
if (!mainWin.isMaximized()) {
mainWin.maximize() // 让当前窗口最大化
} else {
mainWin.restore() // 回到原始的状态
}
})
aBtn[2].addEventListener('click', () => {
// 实现最小化
if (!mainWin.isMinimized()) {
mainWin.minimize()
}
})
})
阻止窗口关闭
const { remote } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
// 阻止窗口关闭
window.onbeforeunload = function () {
let oBox = document.getElementsByClassName('isClose')[0]
oBox.style.display = 'block'
let yesBtn = oBox.getElementsByTagName('span')[0]
let noBtn = oBox.getElementsByTagName('span')[1]
yesBtn.addEventListener('click', () => {
mainWin.destroy()
})
noBtn.addEventListener('click', () => {
oBox.style.display = 'none'
})
return false
}
// 利用 remote 可以获取当前窗口对象
let mainWin = remote.getCurrentWindow()
// 获取元素添加点击操作的监听
let aBtn = document.getElementsByClassName('windowTool')[0].getElementsByTagName('div')
aBtn[0].addEventListener('click', () => {
// 当前事件发生后说明需要关闭窗口
mainWin.close()
})
})
父子及模态窗口
const { remote } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
let oBtn = document.getElementById('btn')
oBtn.addEventListener('click', () => {
let subWin = new remote.BrowserWindow({
parent: remote.getCurrentWindow(),
width: 200,
height: 200,
modal: true
})
subWin.loadFile('sub.html')
subWin.on('close', () => {
subWin = null
})
})
})
自定义菜单角色及类型
const { app, BrowserWindow, Menu } = require('electron')
const createWindow = function () {
let mainWin = new BrowserWindow({
show: false,
width: 800,
height: 600,
icon: './lg.ico',
title: '拉勾教育',
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
})
// 01 自定义菜单的内容
let menuTemp = [
{
label: '角色',
submenu: [
{ label: '复制', role: 'copy' },
{ label: '剪切', role: 'cut' },
{ label: '粘贴', role: 'paste' },
{ label: '最小化', role: 'minimize' },
]
},
{
label: '类型',
submenu: [
{ label: '选项1', type: 'checkbox' },
{ label: '选项2', type: 'checkbox' },
{ label: '选项3', type: 'checkbox' },
{ type: "separator" },
{ label: 'item1', type: "radio" },
{ label: 'item2', type: "radio" },
{ type: "separator" },
{ label: 'windows', type: 'submenu', role: 'windowMenu' }
]
},
{
label: '其它',
submenu: [
{
label: '打开',
icon: './open.png',
accelerator: 'ctrl + o',
click() {
console.log('open操作执行了')
}
}
]
}
]
// 02 依据上述的数据创建一个 menu
let menu = Menu.buildFromTemplate(menuTemp)
// 03 将上述的菜单添加至 app 身上
Menu.setApplicationMenu(menu)
mainWin.loadFile('index.html')
mainWin.on('ready-to-show', () => {
mainWin.show()
})
mainWin.on('close', () => {
mainWin = null
})
}
app.on('ready', () => {
createWindow()
})
app.on('window-all-closed', () => {
app.quit()
})
动态创建菜单
const { remote } = require('electron')
const Menu = remote.Menu
const MenuItem = remote.MenuItem
window.addEventListener('DOMContentLoaded', () => {
// 获取要应的元素
let addMenu = document.getElementById('addMenu')
let menuCon = document.getElementById('menuCon')
let addItem = document.getElementById('addItem')
// 自定义全局变量存放菜单项
let menuItem = new Menu()
// 生成自定义的菜单
addMenu.addEventListener('click', () => {
// 创建菜单
let menuFile = new MenuItem({ label: '文件', type: 'normal' })
let menuEdit = new MenuItem({ label: '编辑', type: 'normal' })
let customMenu = new MenuItem({ label: '自定义菜单项', submenu: menuItem })
// 将创建好的自定义菜单添加至 menu
let menu = new Menu()
menu.append(menuFile)
menu.append(menuEdit)
menu.append(customMenu)
// 将menu 放置于 app 中显示
Menu.setApplicationMenu(menu)
})
// 动态添加菜单项
addItem.addEventListener('click', () => {
// 获取当前 input 输入框当中的内容
let con = menuCon.value.trim()
if (con) {
menuItem.append(new MenuItem({ label: con, type: 'normal' }))
menuCon.value = ''
}
})
})
右键菜单
- 创建一个自定义的菜单内容
- 在鼠标右击行为发生后显示出来
const { remote } = require('electron')
const Menu = remote.Menu
// 定义菜单的内容
let contextTemp = [
{ label: 'Run Code' },
{ label: '转到定义' },
{ type: 'separator' },
{
label: '其它功能',
click() {
console.log('其它功能选项被点击了')
}
},
]
// 依据上述的内容来创建 menu
let menu = Menu.buildFromTemplate(contextTemp)
// 给鼠标右击添加监听
window.addEventListener('DOMContentLoaded', () => {
window.addEventListener('contextmenu', (ev) => {
ev.preventDefault()
menu.popup({ window: remote.getCurrentWindow() })
}, false)
})
主进程与渲染进程通信
const { app, BrowserWindow, ipcMain, Menu } = require('electron')
const createWindow = function () {
let mainWin = new BrowserWindow({
show: false,
width: 800,
title: '拉勾教育',
height: 400,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
}
})
let temp = [
{
label: 'send',
click() {
BrowserWindow.getFocusedWindow().webContents.send('mtp', '来自于自进程的消息')
}
}
]
let menu = Menu.buildFromTemplate(temp)
Menu.setApplicationMenu(menu)
mainWin.loadFile('index.html')
mainWin.webContents.openDevTools()
mainWin.on('ready-to-show', () => {
mainWin.show()
})
mainWin.on('close', () => {
mainWin = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
app.quit()
})
// 主进程接收消息操作
ipcMain.on('msg1', (ev, data) => {
console.log(data)
ev.sender.send('msg1Re', '这是一条来自于主进程的异步消息')
})
ipcMain.on('msg2', (ev, data) => {
console.log(data)
ev.returnValue = '来自于主进程的同步消息'
})
const { ipcRenderer } = require('electron')
window.onload = function () {
// 获取元素
let aBtn = document.getElementsByTagName('button')
// 01 采用异步的 API 在渲染进程中给主进程发送消息
aBtn[0].addEventListener('click', () => {
ipcRenderer.send('msg1', '当前是来自于渲染进程的一条异步消息')
})
// 02 采用同步的方式完成数据通信
aBtn[1].addEventListener('click', () => {
let val = ipcRenderer.sendSync('msg2', '同步消息')
console.log(val)
})
// 当前区域是接收消息
ipcRenderer.on('msg1Re', (ev, data) => {
console.log(data)
})
ipcRenderer.on('mtp', (ev, data) => {
console.log(data)
})
}
渲染进程间通信
- 通过 localStorage 通信
基于主进程的进程通信
// 接收其它进程发送的数据,然后完成后续的逻辑
ipcMain.on('openWin2', (ev, data) => {
// 接收到渲染进程中按钮点击信息之后完成窗口2 的打开
let subWin1 = new BrowserWindow({
width: 400,
height: 300,
parent: BrowserWindow.fromId(mainWinId),
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
})
subWin1.loadFile('subWin1.html')
subWin1.on('close', () => {
subWin1 = null
})
// 此时我们是可以直接拿到 sub 进程的窗口对象,因此我们需要考虑的就是等到它里面的所有内容
// 加载完成之后再执行数据发送
subWin1.webContents.on('did-finish-load', () => {
subWin1.webContents.send('its', data)
})
})
ipcMain.on('stm', (ev, data) => {
// 当前我们需要将 data 经过 main 进程转交给指定的渲染进程
// 此时我们可以依据指定的窗口 ID 来获取对应的渲染进程,然后执行消息的发送
let mainWin = BrowserWindow.fromId(mainWinId)
mainWin.webContents.send('mti', data)
})
Dialog 模块
const { remote } = require('electron')
window.onload = function () {
let oBtn = document.getElementById('btn')
let oBtnErr = document.getElementById('btnErr')
oBtn.addEventListener('click', () => {
remote.dialog.showOpenDialog({
defaultPath: __dirname,
buttonLabel: '请选择',
title: '拉勾教育',
properties: ['openFile', 'multiSelections'],
filters: [
{ "name": '代码文件', extensions: ['js', 'json', 'html'] },
{ "name": '图片文件', extensions: ['ico', 'jpeg', 'png'] },
{ "name": '媒体类型', extensions: ['avi', 'mp4', 'mp3'] }
]
}).then((ret) => {
console.log(ret)
})
})
oBtnErr.addEventListener('click', () => {
remote.dialog.showErrorBox('自定义标题', '当前错误内容')
})
}
shell 与 iframe
- shell 为在浏览器标签页中打开链接
- iframe 是在桌面应用中打开
const { app, BrowserWindow, shell, Menu } = require('electron')
// ...
shell.openExternal('https://kaiwu.lagou.com/')
消息通知
- 基于 H5 实现消息通知
window.onload = function () {
let oBtn = document.getElementById('btn')
oBtn.addEventListener('click', () => {
let option = {
title: '拉勾教育',
body: '互联网人的实战大学,大前端',
icon: './msg.png'
}
let myNotification = new window.Notification(option.title, option)
myNotification.onclick = function () {
console.log('点击了消息页卡')
}
})
}
注册快捷键
const { app, BrowserWindow, globalShortcut } = require('electron')
app.on('ready', () => {
// 注册
let ret = globalShortcut.register('ctrl + q', () => {
console.log('快捷键注册成功')
})
if (!ret) {
console.log('注册失败')
}
console.log(globalShortcut.isRegistered('ctrl + q'))
console.log(ret, '~~~~~')
})
app.on('will-quit', () => {
console.log(666)
globalShortcut.unregister('ctrl + q')
globalShortcut.unregisterAll()
})
剪切版操作
const { clipboard, nativeImage } = require('electron')
window.onload = function () {
// 获取元素
let aBtn = document.getElementsByTagName('button')
let aInput = document.getElementsByTagName('input')
let oBtn = document.getElementById('clipImg')
let ret = null
aBtn[0].onclick = function () {
// 复制内容
ret = clipboard.writeText(aInput[0].value)
}
aBtn[1].onclick = function () {
// 粘贴内容
aInput[1].value = clipboard.readText(ret)
}
oBtn.onclick = function () {
// 将图片放置于剪切板当中的时候要求图片类型属于 nativeImage 实例
let oImage = nativeImage.createFromPath('./msg.png')
clipboard.writeImage(oImage)
// 将剪切板中的图片做为 DOM 元素显示在界面上
let oImg = clipboard.readImage()
let oImgDom = new Image()
oImgDom.src = oImg.toDataURL()
document.body.appendChild(oImgDom)
}
}