Electron悬浮球是很实用功能。其实悬浮球的本质就是Electron多窗口。
对于悬浮球功能的实现,主要分几个功能:
- 主窗口: 为Electron展示窗口,也是主页面
- 子窗口:悬浮球窗口,也是动态窗口,需要显隐
- 全局类:所有窗口和公共方法都存在此类中,这样保证不同窗口之间方法相互调用方法,例如悬浮球的显示和隐藏方法
- render进程与Electron通信交互:通过contextBridge.exposeInMainWorld将Electron通信抛给render进程,实现Electron与render进程通信。
主窗口
/** electron 模块可以用来控制应用的生命周期和创建原生浏览窗口 */
const { app, BrowserWindow, globalShortcut, ipcMain } = require('electron');
const path = require('path');
const url = require('url');
/** 悬浮球窗口 */
const { createHoverBallWindow } = require('./server/hoverBall.ts');
/** 全局窗口 */
const { GlobalWin } = require('./server/golobalWin.ts');
/** 全局鼠标事件监听 */
require('./mainEvent.ts');
/** 获取在 package.json 中的命令脚本传入的参数,来判断是开发还是生产环境 */
const mode = process.argv[2];
const createWindow = () => {
/** 创建浏览窗口 */
const mainWindow = new BrowserWindow({
width: 800,
height: 500,
/** 支持窗口最小化 */
minimizable: true,
/** 窗口是否支持最大化,默认支持 */
maximizable: true,
webPreferences: {
/** 开启沙盒则preload脚本被禁用,所以得设为false */
sandbox: false,
/** 关闭检测同源策略 */
webSecurity: false,
/** 将脚本加入此窗口 */
preload: path.join(__dirname, 'src/preload/main.ts')
}
})
/** 判断是否是开发模式, 此项目是通过create-react-app创建项目 */
if(mode === 'dev') {
mainWindow.loadURL("http://localhost:3000/")
} else {
mainWindow.loadURL(url.format({
pathname:path.join(__dirname, './build/index.html'),
protocol:'file:',
slashes:true
}))
}
/** 键盘事件注册 */
globalShortcut.register('Ctrl+Shift+D', () => {
mainWindow.show()
})
/** 打开开发工具 */
// mainWindow.webContents.openDevTools()
GlobalWin.setMainWin(mainWindow);
}
/** 这段程序将会在 Electron 结束初始化, 和创建浏览器窗口的时候调用, 部分 API 在 ready 事件触发后才能使用。*/
app.whenReady().then(() => {
/** 在窗口里面接收ipcRender发送的请求 */
ipcMain.on('do-a-thing', (event) => {
console.log('&&&&&&&&&')
console.log(event)
})
ipcMain.on('hover-ball-one', (event) => {
console.log('我是悬浮球事件')
})
/** 创建主窗口 */
createWindow();
/** 悬浮球窗口 */
createHoverBallWindow();
/** 第一次渲染,隐藏悬浮球 */
GlobalWin.handleHoverCancle();
app.on('activate', () => {
/** 在 macOS 系统内, 如果没有已开启的应用窗口,点击托盘图标时通常会重新创建一个新窗口 */
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
/** 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此, 通常对应用程序和它们的菜单栏来说应该时刻保持激活状态, 直到用户使用 Cmd + Q 明确退出 */
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
/** 开机自启 */
app.setLoginItemSettings({
/** 登录时打开应用程序, false 将应用从登录启动项中删除。 默认值为 false */
openAtLogin: true,
openAsHidden: true,
})
主窗口脚本,通过contextBridge.exposeInMainWorld将方法暴露给render进程,通过ipcRenderer来进行通信,这样在render进程里面就可以使用window.api.doThing()来实现给主窗口发送请求,也可以带上参数实现带参通信。
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('api', {
doThing: () => ipcRenderer.send('do-a-thing')
})
子窗口
const { app, BrowserWindow } = require('electron');
const { GlobalWin} = require('./golobalWin.ts');
const path = require('path');
const createHoverBallWindow = () => {
const hoverBallMain = new BrowserWindow({
width: 30,
height: 30,
maxWidth: 30,
maxHeight: 30,
minimizable: true,
maxmizable: true,
/** 跳过任务栏显示 */
skipTaskbar: true,
/** 关闭阴影效果 否则设置了窗口透明清空下 透明处会显示阴影效果 */
hasShadow: false,
/** 设置窗口透明 */
transparent: true,
/** 设置窗口透明色 */
backgroundColor: '#0000',
/** 去除窗口边框 */
frame: false,
/** 可调整大小 */
resizable: false,
// 自动隐藏菜单栏
autoHideMenuBar: true,
focusable: false,
alwaysOnTop: true,
webPreferences: {
sandbox: false,
preload: path.join(__dirname, '../preload/hoverBall.ts')
}
})
/** 子窗口的render直接使用html文件,因此使用loadFile来引入 */
hoverBallMain.loadFile(path.join(__dirname, '../render/hoverBall.html'))
/** 将子窗口实例存到公共类里面,这样可以在其他窗口调用 */
GlobalWin.setHoverBall(hoverBallMain);
}
const { contextBridge, ipcRenderer } = require('electron')
const handleHover = () => {
ipcRenderer.send('hover-ball-one')
}
contextBridge.exposeInMainWorld('hoverBall', {
handleHover
})
<!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>
<div id="hoverBall">悬浮球</div>
<script>
const dom = document.getElementById('hoverBall');
dom.addEventListener('click', () => {
/** 在render进程调用子窗口脚本暴露出来的方法 */
window.hoverBall.handleHover()
})
</script>
</body>
</html>
公共类
/** 全局窗口配置 */
class GlobalWin {
/**
* 主窗口
*/
static mainWin;
/**
* 悬浮球窗口
*/
static hoverBall;
/**
* 设置主窗口
* @param mainWin
*/
static setMainWin(mainWin) {
GlobalWin.mainWin = mainWin;
}
/**
* 设置悬浮球窗口
*/
static setHoverBall(hoverBallWin) {
GlobalWin.hoverBall = hoverBallWin;
}
/**
* 隐藏悬浮球方法
*/
static handleHoverCancle() {
GlobalWin.hoverBall.hide();
}
/**
* 显示悬浮球方法
*/
static handleHoverShow() {
GlobalWin.hoverBall.show();
}
}
全局鼠标事件监听
const { app } = require('electron');
const {uIOhook} = require('uiohook-napi');
const path = require('path');
uIOhook.start()
/**
* 鼠标单击按下事件
*/
uIOhook.on('mousedown', (e) => {
// console.log('*********')
// console.log(e)
})
/**
* 鼠标单击放开事件
*/
uIOhook.on('mouseup', async (e) => {
console.log(e
})
总结
以上就是实现一个悬浮球代码,我们设置了主/子窗口,也通过脚本来实现露各个窗口里面的方法,在方法里面可以触发向窗口的通信。也实现了在render进程里面向窗口发送请求。同时我们实现了公共类,将窗口实例,公共方法存放在公共类,包括悬浮球显示隐藏方法,还有通过uiohook-napi对全局鼠标事件进行监听。因此,无论点击显示,render进程操作显示都可以实现。