# Electron 入门 > WARNING: 文章部分内容已经过时,仅适用于 Electron ^11.2.1 ## 技术架构 Electron = Chromium + Node.js + Native APIs ## 工作流程 启动APP -> 主进程创建window -> win加载界面 -> xxx行为 ### 主进程 - 只有主进程才可以进行GUI的API操作 - 只有一个 ### 渲染进程 - 可以有多个 - 和主进程通信完成原生API操作 ## 环境搭建 1. 使用官方 Quick Start ```bash $ git clone https://github.com/electron/electron-quick-start $ cd electron-quick-start $ npm install ``` ```javascript // 01 创建一个窗口 // 02 让窗口加载了一个界面,这个界面就是用Web技术实现, 运行在渲染进程 function createWindow () { // Create the browser window. const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } }) // and load the index.html of the app. mainWindow.loadFile('index.html') // Open the DevTools. // mainWindow.webContents.openDevTools() // ... } ``` 2. 手动创建项目 ```javascript // main.js const {app, BrowserWindow} = require('electron') // 导包 // 当 app 启动之后执行窗口创建等操作 app.whenReady().then(() => { const mainWin = new BrowserWindow({ width: 600, height: 400 }) // 在当前窗口中加载指定界面让它显示具体的内容 mainWin.loadFile('index.html') mainWin.on('close', () => { // ... }) }) app.on('window-all-closed', () => { // ... app.quit() }) ``` ```html 这个窗口标题

嘿!这是一段文字!

``` ```json // package.json { "name": "xxxx", // 项目的名字 "version": "x.x.x", // 项目的版本 "description": "", // 描述 "main": "main.js", // 入口文件 "scripts": { "start": "electron ." // npm start }, "keywords": [], // 关键词? "author": "", // 作者 "license": "ISC", // 协议 "devDependencies": { "electron": "^11.2.1" // 开发依赖 } } ``` ## 生命周期 ready: app初始化完成 dom-ready: 一个窗口中的文本加载完成 did-finsh-load: 导航完成时出发 window-all-closed: 所有窗口都被关闭时触发 before-quit: 在关闭窗口之前触发 will-quit: 在窗口关闭并且应用退出时触发 quit: 当所有窗口被关闭时触发 closed: 当窗口关闭时触发,此时删除窗口引用 ```javascript const { app, BrowserWindow } = require('electron') function createWindow() { let mainWin = new BrowserWindow({ width: 600, height: 400 }) mainWin.loadFile('index.html') mainWin.webContents.on('did-finish-load', () => { console.log('3 - did finish load') }) mainWin.webContents.on('dom-ready', () => { console.log('2 - dom ready') }) mainWin.on('close', () => { console.log('8 - win closed') // 多窗口时最后触发 mainWin = null // 删除引用,释放空间 }) } app.on('ready', () => { console.log('1 - ready') createWindow() }) app.on('window-all-closed', () => { console.log('4 - window all closed') app.quit() }) app.on('before-quit', () => { console.log('5 - before quit') }) app.on('will-quit', () => { console.log('6 - will-quit') }) app.on('quit', () => { console.log('7 - quit') }) ``` ## 窗口大小 ```javascript function createWindow() { let mainWin = new BrowserWindow({ x: 100, // 相对于屏幕左上角 y: 100, show: false, // 防止首页白屏 width: 800, height: 400, maxHeight: 600, // 最大值 maxWidth: 1000, minHeight: 200, // 最小值 minWidth: 300, resizable: false // 固定大小,禁止缩放 }) // 防止首页白屏,否则不显示 mainWin.on('ready-to-show', () => { mainWin.show() }) // ... } ``` ## 窗口标题及环境 ```javascript // main.js function createWindow() { let mainWin = new BrowserWindow({ width: 800, height: 600, show: false, title: "标题", // 要在网页里的标题去除 icon: "w.png", // 设置图标 frame: true, // 是否显示标题栏 transparent: false, // 透明窗体 autoHideMenuBar: true, // 隐藏菜单 webPreferences: { nodeIntegration: true, // Node集成环境 enableRemoteModule: true, // 开启远程模块 remote } }) // ... } ``` ```javascript // index.js const { remote } = require('electron') window.addEventListener('DOMContentLoaded', () => { // 点击按钮打开新窗口 const oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { // 创建窗口 let indexMain = new remote.BrowserWindow({ width: 200, height: 200 }) indexMain.loadFile('list.html') indexMain.on('close', () => { indexMain = null }) }) }) ``` ## 自定义窗口的实现 ```html Document

主体内容

``` ```javascript // index.js const { remote } = require('electron') window.addEventListener('DOMContentLoaded', () => { // 利用 remote 获取当前窗口对象 let mainWin = remote.getCurrentWindow() // 获取元素添加点击操作的监听 let aBtns = document.getElementsByClassName('function') aBtns[0].addEventListener('click', () => { // 最小化 mainWin.minimize() }) aBtns[1].addEventListener('click', () => { // 最大化 mainWin.isMaximized() ? mainWin.restore() : mainWin.maximize() }) aBtns[2].addEventListener('click', () => { // 关闭窗口操作 mainWin.close() }) }) ``` ## 阻止窗口关闭 ```html
是否关闭?
``` ```javascript // index.js const { remote } = require('electron') window.addEventListener('DOMContentLoaded', () => { window.onbeforeunload = function () { let oBox = document.getElementsByClassName('dialog')[0] oBox.style.display = 'block' let yBtn = oBox.getElementsByClassName('confirm')[0] let nBtn = oBox.getElementsByClassName('confirm')[1] yBtn.addEventListener('click', () => { mainWin.destroy() // 不用 close 防止死循环 }) nBtn.addEventListener('click', () => { oBox.style.display = 'none' }) return false } // ... } ``` ## 父子及模态窗口 ```javascript // index.js window.addEventListener('DOMContentLoaded', () => { let oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { let subWin = new remote.BrowserWindow({ // ... parent: remote.getCurrentWindow(), // 设置父窗口 modal: true, // 设置模态窗口 }) // ... }) }) ``` ## 自定义菜单 [Electorn文档 Menu部分](https://www.electronjs.org/zh/docs/latest/api/menu) ```javascript // ... console.log(process.platform) // 平台判断 // ... const createWindow = () => { let mainWin = new BrowserWindow({ // ... }); // 定义自己需要的菜单项 let menuTemp = [ { label: "文件", submenu: [ { label: "打开文件", click() { console.log("打开文件夹") } }, { type: 'separator' // 分隔符 }, { label: "关闭文件夹" }, { label: "关于", role: 'about' }, ], }, { label: "编辑" }, ]; // 利用上述模板生成一个菜单项 let menu = Menu.buildFromTemplate(menuTemp); // 添加到应用里 Menu.setApplicationMenu(menu); // ... }; // ... ``` ## 菜单角色及类型 ```javascript function createWindow() { let mainWin = new BrowserWindow({ // ... }) // 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) // ... ``` ## 动态创建菜单 ```html ``` ```javascript // index.js 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.vaule.trim() if (con) { menuItem.append(new MenuItem({ label: con, type: 'normal' })) menuCon.vaule = '' } }) }) ``` ## 右键菜单的创建 ```javascript // index.js const { remote } = require('electron') const Menu = remote.Menu let contextTemp = [ {lable: 'Run Code'}, {lable: '转到定义'}, {type: 'separator'}, { lable: '其他功能', click() { alert('其他功能选项'); } }, ] let menu = Menu.buildFromTemplate(contextTemp) // 给鼠标添加监听 window.addEventListener('DOMContentLoaded', () => { window.addEventListener('contextmenu', (ev) => { ev.preventDefault() menu.popup({window: remote.getCurrentWindow()}) }, false) }) /** * 01 创建一个自定义的菜单内容 * 02 在鼠标右击行为发生后显示出来 */ ``` ## 主进程与渲染进程通信 ```javascript // main.js const createWindow = () => { let mainWin = new BrowserWindow({ // ... }) let temp = [ { label: 'send', click() { // 主进程主动与渲染进程发送消息 BrowserWindow.getFocusedWindow().webContents.send('mtp', '来自于主进程的消息') } } ] let menu = Menu.buildFromTemplate(temp) // ... } // ... // 主进程接收消息操作 ipcMain.on('msg1', (ev, data) => { console.log(data) ev.sender.send('msg1Re', '来自于主进程的异步消息') }) ipcMain.on('msg2', (ev, data) => { console.log(data) ev.returnValue = '来自于主进程的同步消息' }) ``` ```javascript // index.js const { ipcRenderer } = require('electron') window.onload = () => { // 获取元素 let aBtn = document.getElementsByTagName('button') // 01 采用异步的 API 在渲染进程中给主进程发送信息 aBtn[0].addEventListener('click', () => { ipcRenderer.send('msg1', '来自于渲染进程的一条异步消息') }) // 02 采用同步的 API 在渲染进程中给主进程发送信息 aBtn[1].addEventListener('click', () => { let val = ipcRenderer.sendSync('msg2', '来自于渲染进程的一条同步消息') alert(val) }) // 接收消息的区域 ipcRenderer.on('msg1Re', (ev, data) => { alert(data) }) ipcRenderer.on('mtp', (ev, data) => { alert(data) }) } ``` ## 渲染进程间通信 ### 通过本地储存存储 ```javascript // main.js // ... // 定义全局变量存放主窗口 Id let mainWinId = null const createWindow = () => { let mainWin = new BrowserWindow({ // ... }) mainWinId = mainWin.id // ... } // 接受其他进程发送的数据,完成后续的逻辑 ipcMain.on('openWin2', () => { // 接收到渲染进程中按钮点击信息之后完成窗口2的打开 let subWin1 = new BrowserWindow({ // ... parent: BrowserWindow.fromId(mainWinId), // 或定义全局变量 // ... }) // ... }) // ... ``` ```javascript // index.js window.onload = () => { // 先获取元素 let oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { ipcRenderer.send('openWin2') // 打开窗口2之后,保存数据至本地储存 localStorage.setItem('name', '啦啦啦啦') }) } ``` ```javascript // subWin1.js window.onload = () => { let oInput = document.getElementById('txt') let val = localStorage.getItem('name') oInput.value = val } ``` ### 通过主进程 ```javascript // main.js // 定义全局变量存放主窗口 Id let mainWinId = null const createWindow = () => { let mainWin = new BrowserWindow({ // ... }) mainWinId = mainWin.id // ... } // 接受其他进程发送的数据,完成后续的逻辑 ipcMain.on('openWin2', (_, data) => { // 接收到渲染进程中按钮点击信息之后完成窗口2的打开 let subWin1 = new BrowserWindow({ // ... }) // ... // 由 index -> main* -> subWin // 转交给进程 subWin1.webContents.on('did-finish-load', () => { subWin1.webContents.send('its', data) }) }) // 由 subWin -> main* -> index ipcMain.on('stm', (_, data) => { // 转交给进程 let mainWin = BrowserWindow.fromId(mainWinId) mainWin.webContents.send('mti', data) }) // ... ``` ```javascript // index.js window.onload = () => { // 先获取元素 let oBtn = document.getElementById('btn') // 由 index* -> main -> subWin1 oBtn.addEventListener('click', () => { ipcRenderer.send('openWin2', '来自于 index 进程') }) // 由 subWin1 -> main -> index* // 接受消息 ipcRenderer.on('mti', (_, data) => { alert(data) }) } ``` ```javascript // subWin1.js window.onload = () => { // ... // 在 sub 中发送数据给 index.js let oBtn = document.getElementById('btn') // subWin1* -> main -> index oBtn.addEventListener('click', () => { ipcRenderer.send('stm', '来自于sub进程') }) // index -> main -> subWin1* // 接收数据 ipcRenderer.on('its', (_, data) => { alert(data) }) } ``` ## Dialog模块 [Electron官网 Dialog文档](https://www.electronjs.org/zh/docs/latest/api/dialog) ```javascript // index.js window.onload = () => { let oBtn = document.getElementById('btn') let oBtnErr = document.getElementById('btnErr') oBtn.addEventListener('click', () => { remote.dialog.showOpenDialog({ defaultPath: __dirname, // 默认打开目录 buttonLabel: '请选择', // 按钮上的文字 title: '啦啦啦啦', // 对话框标题 properties: ['openFiles', 'multiSelections'], // 文件类型 filters: [ // 文件类型过滤 {'name': '代码文件', extensions: ['js', 'json', 'html']}, {'name': '图片文件', extensions: ['ico', 'jpeg', 'png']}, {'name': '媒体类型', extensions: ['avi', 'mp4', 'mp3']}, ], }).then(ret => { alert(ret) }) }) oBtnErr.addEventListener('click', () => { remote.dialog.showErrorBox('自定义标题', '当前错误内容') }) } ``` ## shell 与 iframe ### shell ```html

shell and iframe

打开url

``` ```javascript // index.js window.onload = () => { // 1 获取元素 let oBtn1 = document.getElementById('openUrl') let oBtn2 = document.getElementById('openFolder') oBtn1.addEventListener('click', (ev) => { ev.preventDefault() // 阻止默认行为 let urlPath = oBtn1.getAttribute('href') // 打开外部浏览器 shell.openExternal(urlPath) }) oBtn2.addEventListener('click', () => { // 打开当前目录 shell.showItemInFolder(path.resolve(__filename)) }) } ``` ### iframe ```html ``` ```javascript // index.js ipcRenderer.on('open', () => { let iframe = document.getElementById('webview') iframe.src = 'https://cantyonion.site/git/' }) ``` ``` javascript // main.js const createWindow = () => { let mainWin = new BrowserWindow({ // ... }) let tmp = [ { label: '菜单', submenu: [ { label: '关于', click() { shell.openExternal('https://cantyonion.site') } }, { label: '打开', click() { BrowserWindow.getFocusedWindow().webContents.send('open') } }, ] } ] let menu = Menu.buildFromTemplate(tmp) Menu.setApplicationMenu(menu) // ... } // ... ``` ## 消息通知 前往[MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Notification)查看详细内容 ```javascript window.onload = () => { let oBtn = document.getElementById('btn') oBtn.addEventListener('click', () => { let option = { title: '啦啦啦', // 通知标题 body: 'ok,哈哈哈哈', // 信息体 icon: 'c.png' // 图标 } let mayNotification = new window.Notification(option.title, option) mayNotification.onclick = () => { alert("点击了消息页卡") } }) } ``` ## 全局快捷键 ```javascript // main.js // ... app.on('ready', () => { // 注册 let ret = globalShortcut.register('ctrl + q', () => { console.log("快捷键注册成功") }) if (!ret) { console.log('注册失败') } // 判断某个快捷键是否已经注销 console.log(globalShortcut.isRegistered('ctrl + q')) }) app.on('will-quit', () => { globalShortcut.unregister('ctrl + q') // 自动注销使用快捷键 globalShortcut.unregisterAll() }) ``` ## 剪切板模块 ```html

剪切板





``` ```javascript // index.js // ... window.onload = () => { // 获取元素 let aBtn = document.getElementsByTagName('button') let aInput = document.getElementsByTagName('input') let oBtn = document.getElementById('clipImg') let ret = null aBtn[0].onclick = () => { // 复制内容 ret = clipboard.writeText(aInput[0].value) } aBtn[1].onclick = () => { // 粘贴内容 aInput[1].value = clipboard.readText(ret) } oBtn.onclick = () => { // 将图片放置于剪切板的时候要求图片类型属于 nativeImage 类型 let oImage = nativeImage.createFromPath('c.png') clipboard.writeImage(oImage) // 将剪切板中的图片作为 DOM 元素显示在界面上 let oImg = clipboard.readImage() let oImgDom = new Image() oImgDom.src = oImg.toDataURL() // 转换为base64 document.body.appendChild(oImgDom) } } ```