electron
程序员文章站
2022-06-04 19:35:39
...
node GUI
1. NW.js和Electron都是基于NodeJs的GUI框架,使用HTML、CSS、JS来构建UI,处理与用户的交互
2. NW.js和Electron专注于开发桌面跨平台应用,而不是web服务器
3. NW.js的原称为Node-Webkit,目前由因特尔公司维护,而Electron由Github维护;
4. NW.js和Electron都使用了开源浏览器Chromium,并使用NodeJs访问系统、文件、网络...
Electron
1. Electron的原理:将Chromium和Node.js整合到一个运行时环境中,且可以跨平台打包;
1. Chromium是一个谷歌开源浏览器,可以调用所有前端相关的API,且不需要考虑兼容性问题;
2. 浏览器并不能访问原生的资源,但Node.Js的API支持在页面中和操作系统进行交互。
2. npm install -g electron:全局安装Electron
1. 可以在Github上下载Electron的demo:electron-quick-start
2. 进入demo目录,执行 cnpm install 安装依赖,执行 npm start 运行demo
3. electron-forge:相当于Electron的脚手架,可以快速创建、运行、打包项目;
1. npm install -g electron-forge:全局安装
2. electron-forge init myDemo:创建项目,默认使用 npm i 安装依赖;
3. 如果执行失败,可以删除项目中的node_modules,执行 cnpm i,手动安装依赖;
4. 手动搭建项目:创建目录,在目录中创建main.js和index.html,执行cnpm init;
1. main.js就是主进程,项目运行的入口文件;
2. 一个应用只有一个主进程,但可以有多个渲染进程,一个窗口就表示一个渲染进程;
3. electron .:运行项目。
5. 拖放打开文件
<textarea id="container"></textarea>
1. 调用H5的拖动API,获取拖放文件的路径
let box = document.querySelector('#container')
box.ondragenter=box.ondragover=box.ondragleave = (e) => {
return false; --> 组织默认行为
}
box.ondrop=(e) => {
e.preventDefault(); --> 阻止默认行为
let path = e.dataTransfer.files[0].path; --> 获取文件的路径
}
2. 使用Node.js的fs模块读取文件,赋值给box.value:let fs = require('fs')
6. 相关模块
1. Electron中的模块分为:主进程模块,渲染进程模块,公用模块;
2. app:主进程模块,控制应用的生命周期,写法比较固定,注册固定的几个事件;
3. BrowserWindow:主进程模块,窗口相关的模块;
let win = BrowserWindow.getFocusedWindow(); -->获取当前窗口的对象
win.center(); -->把窗口移动到屏幕*
4. remote:渲染进程的模块,用于间接调用部分主进程的模块,如BrowserWindow
5. Menu:主进程的菜单模块,包括顶部菜单、右键菜单、系统托盘的右键菜单。
7. Menu菜单功能必须在窗口加载完成之后才能执行;
app.on('ready', {
...... //创建窗口BrowserWindow
require('./main/menu.js') //加载创建菜单的js模块
})
1. 虽然渲染进程也能通过remote模块调用Menu模块,实现自定义的顶部菜单,但可能会闪现默认
的顶部菜单,所以还是建议在主进程中实现顶部菜单。
进程间通信IPC
1. 被Electron直接运行的脚本(package.json中指定的main节点)称为主进程,有且只有一个;
2. 用于展示的web界面都运行在一个独立的进程中,称为渲染进程;
3. 进程间通信的相关模块:ipcMain(主进程)、ipcRenderer(渲染进程)
主进程与渲染进程
1. 渲染进程向主进程发送异步消息:ipcRenderer.send('事件名1', 消息内容)
1. 主进程中监听消息:ipcMain.on('事件名1', (event, arg) => { ... })
2. 主进程给渲染进程回复消息:event.sender.send('事件名2', 消息内容)
3. 渲染进程监听回复的消息:ipcRenderer.on('事件名2', (event, arg) => {})
2. 渲染进程向主进程发送同步消息:
1. let msg = ipcRenderer.sendSync('事件名1', 消息内容)
2. 与异步消息不同,主进程收到同步消息,必须回复,否则渲染进程会阻塞;
ipcMain.on('事件名1', (event, arg) => {
event.returnValue = 'sync reply' ---> 回复给渲染进程
}) ----> sendSync()的返回值就是主进程回复的消息内容
3. 主进程把数据挂载到node的全局对象global上,渲染进程通过 getGlobal() 获取数据
1. 主进程:global.username = 'abc';
2. 渲染进程:remote.getGlobal('username');
3. 这种方式污染了全局对象global,并不建议使用。
渲染进程与渲染进程
1. 渲染进程无法直接通信,最简单的方式是通过localStorage实现;
2. 通过BrowserWindow和webContents模块实现:
1. webContents是一个事件发出者,负责渲染并控制网页,也是BrowserWindow的属性;
3. 渲染进程1向主进程发送消息,主进程创建渲染进程2,再向渲染进程2发送消息;
ipcMain.on('事件名1', (event, arg) => { --> 主进程监听消息事件
win = new BrowserWindow({width:400, height:300})
win.loadURL(path.join('file:', __dirname, '../news.html'))
win.webContents.on('did-finish-load', () => { --> 窗口加载完毕
win.webContents.send('事件名2', arg) --> 向渲染进程2发送事件
}) ---> 新的渲染进程中注册'事件名2'
})
4. 渲染进程2向渲染进程1回复消息,则需要把渲染进程1的Id也发送给渲染进程2
ipcMain.on('事件名1', (event, arg) => {
//必须在新窗口加载之前获取当前窗口的Id,否则获取的新窗口的Id
let winId = BrowserWindow.getFocusedWindow().id
win = new BrowserWindow({width:400, height:300})
win.loadURL(path.join('file:', __dirname, '../news.html'))
win.webContents.on('did-finish-load', () => {
win.webContents.send('事件名2', arg, winId)
})
})
5. 在新窗口中注册事件,向渲染进程1回复消息,在渲染进程1中监听回复事件
ipcRenderer.on('事件名2', (event, arg, winId) => {
let win = BrowserWindow.fromId(winId) -->获取渲染进程1的窗口对象
win.webContents.send('事件名3', 'reply the msg') -->回复消息
})
shell
1. shell模块可以操作用户的默认浏览器,调用资源管理器;
2. 调用用户的默认浏览器,打开指定网址:shell.openExternal('https://...')
3. shell.showItemInFolder()/openItem():资源管理器的相关操作;
4. <webview>:与HTML的<iframe>相似,但webview与应用程序不在同一个进程;
1. webview没有渲染进程的权限,并且与应用之间的交互都是异步的,保证应用的安全性不受嵌入
内容的影响;
2. <webview src="https://www.baidu.com"></webview>
dialog
1. dialog模块:不仅可以弹出信息提示框,也可以实现本地文件的打开与保存;
2. 信息提示框
1. dialog.showErrorBox('title', 'content'):错误信息提示框;
2. dialog.showMessageBox({}, (index)=>{...}):信息提示框;
3. 异步变同步:let index = dialog.showMessageBox({})
3. 打开文件/目录:dialog.showOpenDialog({}, (path) => {...})
1. 打开功能只是回调文件/目录的路径,并不能获取文件/目录的内容;
2. 操作文件/目录的功能,需要通过nodejs实现。
4. 保存文件:dialog.showSaveDialog({}, (path) => {...})
1. showSaveDialog()只回调保存路径,并不是真的保存,不会在本地生成任何文件;
2. 保存功能需要借助nodejs实现,通过fs模块把内容写入本地。
5. 打印当前窗口:BrowserWindow.getFocusedWindow().webContents.print()
6. app.quit():退出应用,Electron使用的是单例模式,此时不必考虑性能问题。
系统托盘
1. Tray模块:主进程的系统托盘模块;
2. 实现系统托盘:let tray = new Tray(path.join(__dirname, 'icon.png'))
3. 设置托盘名称:tray.setToolTip('title'),鼠标悬停在托盘图标上弹出;
4. 创建托盘的右键菜单
1. Menu模块创建菜单:let menu = Menu.buildFromTemplate([{...}, {...}])
2. 设置托盘的右键菜单:tray.setContextMenu(menu)
5. 关闭窗口,则隐藏到系统托盘;双击系统托盘,则重新打开窗口
1. 关闭窗口
let win = BrowserWindow.getFocusedWindow() --> 获取当前的窗口对象
win.on('close', (e) => { --> 监听关闭按钮的事件
if(win.isFocused()) { -->点击托盘右键菜单上的'退出',则关闭窗口
win = null
} else {
e.preventDefault() --> 阻止默认的关闭事件
win.hide() --> 隐藏窗口到系统托盘
}
})
2. app.quit()会回调'close'事件,所以需要判断当前关闭的窗口是否拥有焦点;
3. 如果当前窗口拥有焦点,则隐藏窗口;如果没有焦点,表示窗口已经处于隐藏状态,则关闭窗口
4. 打开窗口
tray.on('double-click', () => { --> 监听系统托盘的双击事件
win.show()
})
6. 托盘图标的闪烁:设置定时器setInterval(),每隔一段时间就重新设置一次图标。
消息通知
1. Electron的消息通知是通过H5的API实现的,右下方弹出通知框的效果;
let option = {
title: '通知标题', body: '通知内容', icon: '通知图标的路径'
}
let notification = new window.Notification(option.title, option)
notification.onclick = ()=>{...} --> 通知框的点击事件
2. 监听网络变化:也是通过H5实现
1. 网络正常:window.addEventListener('online', () => { ... })
2. 网络断开:window.addEventListener('offline', () => { ... })
其他模块
1. globalShortcut模块:主进程模块,注册全局快捷键,在窗口创建之前注册;
app.on('ready', () => {
... //注册全局快捷键 ---> ... //创建窗口 ---> ... //设置菜单
})
1. 注册全局快捷键:globalShortcut.register('ctrl+e', () => { ... })
2. 判断是否注册成功:globalShortcut.isRegistered('ctrl+e')
3. 窗口关闭时要取消全局快捷键:globalShortcut.unregister('ctrl+e')
app.on('will-quit', () => { globalShortcut.unregister('ctrl+e') })
2. clipboard模块:公用模块,剪切板事件
1. 复制操作:clipboard.writeText('复制内容'),系统的Ctrl+C
2. 手动获取复制的内容:let text = clipboard.readText(),系统的Ctrl+V
3. 除了读取文本,剪切板还能读取HTML、Image、RTF...
3. nativeImage模块:公用模块,用于读取、传递图片
1. nativeImage.createEmpty():创建一个空的nativeImage对象;
2. nativeImage.createFromPath('路径'):根据图片路径,创建nativeImage对象
4. 剪切板无法直接读取图片,需要借助nativeImage模块;
let image = nativeImage.createFromPath('static/back.jpg')
clipboard.writeImage(image) --> 复制图片
let data = clipboard.readImage() --> 读取复制的图片,返回nativeImage对象
let src = data.toDataURL() --> 转为图片的Base64编码
1. 创建一个<img />:let img = new Image()
2. 设置src属性:img.src = src
3. 插入到HTML中:document.body.appendChild(img)
Electron-Vue
1. Electron-Vue:Electron结合Vue开发单页面桌面应用;
1. 安装环境:cnpm install electron -g、cnpm install vue-cli -g
2. 创建项目:vue init simulatedgreg/electron-vue my-project
3. 下载依赖:cd my-project ---> cnpm install
4. 运行项目:npm run dev
2. 在main.js中默认引入了electron,在Vue组件中通过this.$electron能直接使用;
1. 主进程模块:src/main/index.js
2. 渲染进程模块:src/renderer目录下的所有Vue组件
3. 在Vue组件中使用Node.js的模块:const path = require('path');
4. 打包后的electron应用无法找到图片
1. 对于static目录下的图片:<img :src="'./static/img/a.png'" />
2. 对于电脑本地的图片,直接使用图片的绝对路径,另外还要关闭窗口的安全模式
new BrowserWindow({ webPreferences: { webSecurity: false } });
窗口操作
1. frame:false:无边框化,去除顶部菜单、最小化、最大化和关闭;
let win = new BrowserWindow({..., useContentSize:true, frame:false})
1. 无边框化之后,窗口的可拖拽区也随之消失,需要手动设置拖拽区,鼠标可以拖动窗口;
2. 设置拖拽区的CSS属性:-webkit-app-region: drag
body{ -webkit-app-region: drag } --> 设置整个窗口为拖拽区
3. 拖拽区内的按钮必须标记为no-draggable,否则将不可点击;
button{ -webkit-app-region: no-drag; }
2. win.setMenu(null):只隐藏顶部菜单;
3. 最大化、最小化、隐藏、关闭窗口
1. win.hide():隐藏窗口; win.isVisible():窗口是否可见; win.close():关闭窗口
2. win.maximize():最大化窗口,如果窗口尚未显示,这也将会显示,但不会有焦点;
3. win.unmaximize():取消窗口最大化; win.isMaximized():判断窗口是否最大化;
5. win.minimize():最小化窗口; win.isMinimized():判断窗口是否最小化;
6. win.restore():将窗口从最小化状态恢复到以前的状态。
NeDB
1. NeDB:Nodejs实现的一个NoSQL嵌入式数据库;
1. 与Android上的Sqlite类似,都是嵌入式数据库,一般用于本地数据的持久化;
2. 不同的是,Sqlite是MySql类型的,使用SQL语句操作,而NeDB的操作类似于MongoDB;
3. 可以充当内存数据库,也可以用来实现本地存储,甚至可以在浏览器中使用。
2. 使用方式:
1. 安装操作模块:cnpm install nedb --save
2. 在src/renderer目录下创建datastore.js,import Datastore from 'nedb'
import path from 'path' import {remote} from 'electron'
export default new Datasotre({
autoload: true,
filename: path.join(remote.app.getPath('userData'), '/data.db')
})
3. 设置NeDB保存在userData目录中,该目录空间专门为应用程序所保留,不会被篡改;
4. 在main.js中,把数据库操作对象绑定到原型上:
import db from './datastore' --> Vue.prototype.$db = db
上一篇: CentOS7.6安装nginx全过程
下一篇: electron主进程通信渲染进程
推荐阅读
-
蒲公英 · JELLY技术周刊 Vol.05: Rust & Electron 的高性能实践 -- Finda
-
解决Vue+Electron下Vuex的Dispatch没有效果问题
-
electron-vue利用webpack打包实现多页面的入口文件问题
-
详解使用webpack+electron+reactJs开发windows桌面应用
-
用Electron写个带界面的nodejs爬虫的实现方法
-
解决Vue+Electron下Vuex的Dispatch没有效果问题
-
Electron-vue开发的客户端支付收款工具的实现
-
Electron中实现大文件上传和断点续传功能
-
electron-vue利用webpack打包实现多页面的入口文件问题
-
使用 electron 实现类似新版 QQ 的登录界面效果(阴影、背景动画、窗体3D翻转)