Electeron基础之悬浮球

1,603 阅读4分钟

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进程操作显示都可以实现。