Electron 基于 Chromium 和 Node.js, 让你可以使用 HTML, CSS 和 JavaScript 构建应用。
中文官网:https://www.electronjs.org/zh/
名称 | 语音 | 优点 | 缺点 |
---|---|---|---|
QT | C++ | 跨平台、性能好、生态好 | 依赖多,程序包大 |
PyQT | Python | 底层集成度高、易上手 | 授权问题 |
WPF | C# | 类库丰富、扩展灵活 | 只支持Windows,程序包大 |
WinForm | C# | 性能好,组件丰富,易上手 | 只支持Windows,UI差 |
Swing | Java | 基于AWT,组件丰富 | 性能差,UI一般 |
NW.js | JS | 跨平台性好,界面美观 | 底层交互差、性能差,包大 |
Electron | JS | 相比NW发展更好 | 底层交互差、性能差,包大 |
CEF | C++ | 性能好,灵活集成,UI美观 | 占用资源多,包大 |
必须安装node、npm
mkdir demo && cd demo
npm init
执行完npm init后,会提示输入信息,除了entry point这个选项必须为main.js外,其余随意
由于网络原因,安装electron时可能会卡住,需要在项目根路径创建.npmrc
文件,并写入镜像信息
electron_mirror = https://npmmirror.com/mirrors/electron/
或者执行命令:export electron_mirror = https://npmmirror.com/mirrors/electron/
然后再安装
npm install --save-dev electron
由于electron本身没有热启动的功能,所以需要引入依赖来实现
npm install --save-dev nodemon
修改package.json
的main
标签,启动文件为main.js
在package.json
的scripts
标签下,添加"start": "electron ."
,开发环境使用npm start
打开应用
{
"main": "main.js",
"scripts": {
"start": "electron .",
"warm_start": "nodemon --exec electron . . --watch ./ --ext .js,.html,.css,.vue" //热启动
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
</body>
</html>
/** 此处需要引入两个模块
* app 模块,它控制应用程序的事件生命周期。
* BrowserWindow 模块,它创建和管理应用程序窗口。
*/
const {app,BrowserWindow} = require('electron')
function creatWindow(){
win = new BrowserWindow({
width: 1200,
height: 800
})
//加载html文件
win.loadFile("index.html")
//加载链接
win.loadURL("https://www.ygang.top")
//开启开发者工具
win.webContents.openDevTools();
}
app.whenReady().then(() => {
creatWindow()
})
此时,使用npm start
命令,可以打开窗口啦
程序运行在Web环境,可以按照正常的前端写法来写,
index.html
中,不需要引入main.js
,其余的js
正常引入使用就可以
cnpm install -g electron-packager
electron-packager ./ 'HelloWorld' --platform=win32 --arch=x64 --icon=icon.ico --out=./out --asar --app-version=0.0.1 --overwrite --electron-version 16.0.1
参数说明:
./
:被打包的项目路径,当前目录
'HelloWorld'
:打包后的项目名
--platform
:打包哪个平台的应用,可选win32
,linux
,darwin
,mas
,这个也可以换为--all
,打包全平台
--arck
:系统位数,x86,x64
--icon
:应用图标
--out
:打包文件输出目录
--app-version
:应用版本
-asar
:可以不加,影响打包后源码格式
--overwrite
:是否覆盖原来打包的
--electron-version
:Electron版本
可以将打包命令,配置在package.json文件scripts中
{
"scripts": {
"package": "electron-packager ./ 'HelloWorld' --platform=win32 --arch=x64 --icon=icon.ico --out=./out --asar --app-version=0.0.1 --overwrite --electron-version 16.0.1"
}
}
npm run package
主进程:启动项目时运行的main.js
脚本就是我们说的主进程。在主进程运行的脚本可以以创建 Web 页面的形式展示 GUI。主进程只有一个。
渲染进程:每个 Electron 的页面都在运行着自己的进程,这样的进程称之为渲染进程(基于Chromium的多进程结构)。
注意:主进程使用 BrowserWindow 创建实例,主进程销毁后,对应的渲染进程会被终止。主进程与渲染进程通过 IPC 方式(事件驱动)进行通信。
// 当所有窗口关闭
app.on('window-all-closed', () => {
console.log('window-all-closed')
// 对于 MacOS 系统 -> 关闭窗口时,不会直接退出应用
if (process.platform !== 'darwin') {
app.quit()
}
})
// 当应用退出
app.on('quit', () => {
console.log('quit')
})
app.whenReady().then(() => {
createWindow()
// 在MacOS下,当全部窗口关闭,点击 dock 图标,窗口再次打开。
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
由于渲染进程运行于浏览器环境中,所以无法使用require
来导入Node的模块,例如fs
1、通过 webPreferences/nodeIntegration
(不推荐、不安全)
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
//集成node
nodeIntegration: true,
//关闭隔离
contextIsolation: false
}
})
2、通过 webPreferences/preload
实现,也就是所有关于Node模块的代码写在preload.js
中
注意:preload.js并不是主进程,仍然是渲染进程,只是可以调用Node模块
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
//集成node
nodeIntegration: true,
//预加载
preload: path.resolve(__dirname, './preload.js')
}
})
这样做的话,主进程和渲染进程隔离了,如果在渲染进程中需要读取preload.js
中的属性,就需要使用了electron
提供的contextBridge
preload.js
const {contextBridge} = require('electron')
contextBridge.exposeInMainWorld('demoApi',{
platform: process.platform
})
index.js(声明的api会被绑定到window上)
console.log(window.demoApi)
main.js,主进程
const fs = require('fs')
const {ipcMain} = require('electron')
function newFile(content){
fs.writeFile('D:\\桌面\\electron.txt',content,() => {})
return 'success'
}
ipcMain.handle('new-file-event',async (e,arg) => {
return newFile(arg)
})
preload.js,需要将ipcRenderer对象绑定到window对象上,供渲染进程使用
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('ipcRenderer',ipcRenderer)
index.js,渲染进程
document.getElementById('btn').addEventListener('click',() => {
window.ipcRenderer.invoke('new-file-event','hahaha').then((msg) => {
console.log(msg)
})
})
如果需要在渲染进程中,使用主进程的对象,那么需要使用remote模块
如果是electron13以后,那么需要手动加入remote包
npm install --save @electron/remote
主进程共享:
var electron = require('electron') // 引入electron模块
var app = electron.app // 创建electron引用
var BrowserWindow = electron.BrowserWindow; // 创建窗口引用
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; //关闭安全检查
var mainWindow = null ; // 声明要打开的主窗口
app.on('ready',()=>{
mainWindow = new BrowserWindow({})
// 初始化remote
require('@electron/remote/main').initialize()
require('@electron/remote/main').enable(mainWindow.webContents)
mainWindow.loadFile('index.html') // 加载那个页面
// 分享主进程的数据
global.shareObject = {
mainWindow: mainWindow,
Menu:electron.Menu
};
})
渲染进程引入:
// 获取主进程全局变量
const shareObject = require('@electron/remote').getGlobal("shareObject")
var Menu = shareObject.Menu;
var mainWindow = shareObject.mainWindow;
如果需要获取 Electron exe 程序的所在目录
process.cwd()
在应用程序退出之前触发
app.on('before-quit', (e) => {
console.log('App is quiting')
e.preventDefault()
})
browserWindow 失去焦点时触发
app.on('browser-window-blur', (e) => {
console.log('App unfocused')
})
app.on('browser-window-focus', (e) => {
console.log('App focused')
})
退出整个应用
app.quit()
获取系统目录"home" | "appData" | "userData" | "sessionData" | "temp" | "exe" | "module" | "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos" | "recent" | "logs" | "crashDumps"
app.getPath('home')
创建并控制浏览器窗口,new
的时候就会创建显示
const win = new BrowserWindow({ options })
win.loadFile('index.html')
//options属性
width 整数型 (可选) - 窗口的宽度(以像素为单位)。 默认值为 800
height 整数型 (可选) - 窗口的高度(以像素为单位)。 默认值为 600
x Interger (可选) - (必选 如果使用了y) 窗口相对于屏幕左侧的偏移量。 默认值为将窗口居中
y Integer (可选) - (必选 如果使用了x) 窗口相对于屏幕顶端的偏移量。 默认值为将窗口居中
useContentSize boolean (可选) - 根据内容大小调整窗口大小。默认值为 false
center boolean (可选) - 窗口是否在屏幕居中. 默认值为 false
minWidth(可选)-窗口的最小宽度。默认为0 默认值为 0
minHeight Integer(可选) - 窗口的最小高度。 默认值为 0
maxWidth Integer(可选)-窗口的最大宽度。 默认值不限
maxHeight Integer (可选) - 窗口的最大高度。 默认值不限
resizable boolean (可选) - 窗口大小是否可调整。 默认值为 true
movable boolean (可选) macOS Windows - 窗口是否可移动。 在 Linux 上未实现。 默认值为 true
minimizable boolean (可选) macOS Windows - 窗口是否可最小化。 在 Linux 上未实现。 默认值为 true
maximizable boolean (可选) macOS Windows - 窗口是否最大化。 在 Linux 上未实现。 默认值为 true
closable boolean (可选) macOS Windows - 窗口是否可关闭。 在 Linux 上未实现。 默认值为 true
alwaysOnTop boolean (可选) - 窗口是否永远在别的窗口的上面。 默认值为 false
fullscreen boolean (可选) - 窗口是否全屏. 当明确设置为 false 时,在 macOS 上全屏的按钮将被隐藏或禁用. 默认值为 false.
kiosk boolean (可选) - 窗口是否进入kiosk模式。 默认值为 false.
titlestring(可选) - 默认窗口标题 默认为"Electron"。 如果由loadURL()加载的HTML文件中含有标签<title>,此属性将被忽略。
icon (NativeImage | string) (可选) - 窗口图标。 在 Windows 上推荐使用 ICO 图标来获得最佳的视觉效果, 默认使用可执行文件的图标.
show boolean (可选) - 窗口是否在创建时显示。 默认值为 true。
frame boolean (可选) - 设置为 false 时可以创建一个无边框窗口 默认值为 true。
parent BrowserWindow (可选) - 指定父窗口 默认值为 null.
backgroundColor string (可选) - 窗口背景色,格式为 Hex, RGB, RGBA, HSL, HSLA 或 CSS 命名颜色。
hasShadow boolean (可选) - 窗口是否有阴影. 默认值为 true。
opacity number (可选) macOS Windows - 设置窗口的初始透明度,在 0.0(全透明)和 1.0(完全不透明)之间
// 否则new的时候就会显示窗口
let mainWindow = new BrowserWindow({ show: false })
// 页面加载完毕才会显示
mainWindow.once('ready-to-show', () => {
mainWindow.show()
})
mainWindow = new BrowserWindow({
frame: false
})
让页面可拖拽,这个属性也可以加在其他dom元素上
<body style="user-select: none; -webkit-app-region:drag;">
no-drag 修复下面控件的bug
<input style="-webkit-app-region: no-drag;" type="range" name="range" min="0" max="10">
显示红绿灯
mainWindow = new BrowserWindow({
titleBarStyle: 'hidden' // or hiddenInset 距离红绿灯更近
})
设置当前窗口加载的资源
显示或隐藏窗口
//静态方法,获取所有窗口
let allWindows = BrowserWindow.getAllWindows()
窗口最大化和最小化
保持窗口最后的状态(位置、大小)
npm install electron-win-state --save
const WinState = require('electron-win-state').default
const winState = new WinState({
defaultWidth: 1200,
defaultHeight: 800
})
function creatWindow(){
let win = new BrowserWindow({
...winState.winOptions,
// 注意:使用winState管理就不要指定大小了
// width: 1200,
// height: 800,
})
// winState管理当前窗口
winState.manage(win)
win.loadFile("index.html")
}
webContents 是 EventEmitter 的实例,负责渲染和控制网页,是 BrowserWindow 对象的一个属性。
mainWindow.webContents
返回 WebContents[] - 所有 WebContents 实例的数组。 包含所有Windows,webviews,opened devtools 和 devtools 扩展背景页的 web 内容
const {app, BrowserWindow, webContents} = require('electron')
console.log(webContents.getAllWebContents())
资源全部加载完成调用
let wc = mainWindow.webContents
wc.on('did-finish-load', () => {
console.log('Conent fully loaded')
})
Dom完全加载完成调用
wc.on('dom-ready', () => {
console.log('DOM Ready')
})
监听右键点击事件
wc.on('context-menu', (e, params) => {
console.log(`Context menu opened on: ${params.mediaType} at x:${params.x}, y:${params.y}`)
})
在当前窗口执行js
wc.on('context-menu', (e, params) => {
wc.executeJavaScript(`alert('${params.selectionText}')`)
})
显示用于打开和保存文件、警报等的本机系统对话框
const {dialog} = require('electron')
打开文件的dialog
dialog.showOpenDialog({
buttonLabel: '选择',
defaultPath: app.getPath('desktop'),
properties: ['multiSelections', 'createDirectory', 'openFile', 'openDirectory']
}).then((result)=> {
console.log(result)
})
保存文件的dialog
dialog.showSaveDialog({}).then(result => {
console.log(result.filePath)
})
显示消息对话窗
//Yes、No显示的位置会在下面
const answers = [ 'an1','an2', 'an3', 'Yes', 'No',]
dialog.showMessageBox({
title: 'Message Box',
message: 'Please select an option',
detail: 'Message details.',
buttons: answers
}).then(({response}) => {
console.log(`User selected: ${answers[response]}`)
})
快捷键:定义键盘快捷键。 系统快捷键:在应用程序没有键盘焦点时,监听键盘事件。
快捷键可以包含多个功能键和一个键码的字符串,由符号+
结合,用来定义你应用中的键盘快捷键
快捷方式使用 register 方法在 globalShortcut 模块中注册。
globalShortcut 模块可以在操作系统中注册/注销全局快捷键, 以便可以为操作定制各种快捷键。
注意: 快捷方式是全局的; 即使应用程序没有键盘焦点, 它也仍然在持续监听键盘事件。 在应用程序模块发出 ready 事件之前, 不应使用此模块。
globalShortcut.register('CommandOrControl+M', () => {
win.maximize()
})
#特殊功能键
Command (缩写为Cmd)
Control (缩写为Ctrl)
CommandOrControl (缩写为 CmdOrCtrl)
Alt
Option
AltGr
Shift
Super
Space
Tab
大写锁定(Capslock)
数字锁定(Numlock)
设置系统菜单
const {Menu} = require('electron')
const menu = Menu.buildFromTemplate([
{
label: 'Electron',
submenu: [
{ label: 'Item 1'},
{ label: 'Item 2', submenu: [ { label: 'Sub Item 1'} ]},
{ label: 'Item 3'},
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo'},
{ role: 'redo'},
{ role: 'copy'},
{ role: 'paste'},
]
},
{
label: 'Actions',
submenu: [
{
label: 'DevTools',
role: 'toggleDevTools'
},
{
role: 'toggleFullScreen'
},
{
label: 'Greet',
click: () => { console.log('Hello from Main Menu') },
accelerator: 'Shift+Alt+G'
}
]
}
])
Menu.setApplicationMenu(menu)
const {Tray} = require('electron')
// 定义托盘右键菜单
let trayMenu = Menu.buildFromTemplate([
{ label: 'Item 1' },
{
label: '退出',
role: 'quit'
}
])
// 声明全局变量,避免被垃圾回收掉
let tray = null;
function createTray() {
// 托盘图标
tray = new Tray('test.jpg')
// 鼠标悬浮托盘描述
tray.setToolTip('Tray details')
// 托盘的点击事件
tray.on('click', e => {
//当按住shift点击
if (e.shiftKey) {
app.quit()
} else {
//窗口显示或隐藏
win.isVisible() ? win.hide() : win.show()
}
})
tray.setContextMenu(trayMenu)
}
function creatWindow(){
// 创建窗口时创建托盘
createTray()
...
}
在系统剪贴板上进行复制和粘贴操作。
在主进程(main process)和渲染进程(renderer process)上均可用。
const {clipboard} = require('electron')
// 返回字符串 - 剪贴板中的内容为纯文本
clipboard.readText()
// 将文本写入系统剪贴板
clipboard.writeText(text)
访问可用于从桌面捕获音频和视频的媒体源信息。
注意:目前只能在主进程中调用
const {desktopCapturer} = require('electron')
desktopCapturer.getSources({
types: ['window', 'screen'],
thumbnailSize: {
width: 1728,
height: 1117
}
}).then(async sources => {
//sources是一个数组
console.log(sources)
return sources
for (const source of sources) {
// sources是一个数组
console.log(sources)
// 获取一个NativeImage对象
let str = sources[0].thumbnail.crop({ x: 0, y: 30, width: 1200, height: 1170 })
return str.toDataURL()
}
})
使用 PNG 或 JPG 文件创建托盘、dock和应用程序图标。
在 Electron 内, 那些需要图片的 API 可以传递两种参数, 一种是文件路径, 一种是 NativeImage
实例对象。 空的图片对象将被 null
参数替代
// 从剪贴板获取图片
clipboard.readImage()