Skip to content

常见问题

苹果安装失败

如果软件执行时出现 xxx已损坏,无法打开。 你应该将它移到废纸篓

  • 在命令行执行以下代码
  • /Applications/camera.app 参数是从访达>应用程序 中找到应用图标,然后托到命令行即可
 xattr -cr /Applications/camera.app

浏览器打开窗口

下面代码监听渲染进程中请求创建一个新窗口,然后使用系统的默认浏览器打开

//渲染进程中请求创建一个新窗口之前被调用
mainWindow.webContents.setWindowOpenHandler((details) => {
  shell.openExternal(details.url)
  //取消新窗口创建
  return { action: 'deny' }
})

苹果dock图标

下面主进程代码用于隐藏dock栏图标

app.whenReady().then(() => {
  ...
  //隐藏苹果dock图标
  if (process.platform == 'darwin') app.dock.hide()
  //托盘
  createTray()
})

任务栏图标

下面是主进程代码,用于定义任务栏图标

import { Menu, shell, Tray } from 'electron'
import path from 'path'
const createTray = () => {
  const tray = new Tray(
    path.resolve(
      __dirname,
      process.platform == 'darwin'
        ? '../../resources/trayTemplate@2x.png'
        : '../../resources/windowTray.png'
    )
  )
  const contextMenu = Menu.buildFromTemplate([
    { label: '退出程序', role: 'quit' },
    { type: 'separator' },
    { label: '问题反馈', click: () => shell.openExternal('https://www.banmashou.com') }
  ])
  tray.setToolTip('斑马兽摄像头')
  tray.setContextMenu(contextMenu)
}

export { createTray }

窗口托动

当把为窗口属性设置无边框时 frame:false,因为没有了标题栏窗口不能托动。

使用css 控制

new BrowserWindow({
  title: 'Main window',
  width: 414,
  frame: false,
  ...
})

通过定义下面的Electron CSS样式,可以实现托动任何DOM都可以托动窗口。

html {
  -webkit-app-region: drag;
}

定义了上面的样式后,如果页面中存在 textarea 标签时将不能实现托动放大,能过定义以下样式可以解决这个问题

textarea {
  -webkit-app-region: no-drag;
}

鼠标在屏幕中的位置

//获取鼠标在屏幕的 x,y 坐标,根据电脑显示器排列方式,如果主显示器在右边,鼠标在左边显示器,则 x 为负数
const point = screen.getCursorScreenPoint()

鼠标所在的屏幕

const point = screen.getCursorScreenPoint()
//根据鼠标的 x、y 获取当前屏幕
const display = screen.getDisplayNearestPoint(point)

鼠标事件

当给某个子元素设置了 -webkit-app-region: drag 时,JS事件机制对该元素无效。所以会出现,当父元素使用 mouseenter 事件时,离开这个子元素到父元素时,会触发父元素的 mouseenter 事件。

使用 JS控制

下面是示例代码,useDrag.ts 是渲染进程接收 DOM 事件,然后调用主进程改变窗口位置

class Drag {
  private body: HTMLBodyElement | null = null
  constructor(private resPageX = 0, private resPageY = 0) {}
  public run() {
    window.addEventListener('DOMContentLoaded', () => {
      this.body = document.querySelector('body')!
      this.body.addEventListener('mousedown', this.down.bind(this))
    })
  }
  private down(e: MouseEvent) {
    this.resPageX = e.pageX
    this.resPageY = e.pageY
    const mouseEvent = this.move.bind(this)
    this.body!.addEventListener('mousemove', mouseEvent)
    this.body!.addEventListener('mouseup', () =>
      this.body?.removeEventListener('mousemove', mouseEvent)
    )
  }
  private move(e: MouseEvent) {
    const x = e.pageX - this.resPageX
    const y = e.pageY - this.resPageY
    window.api.drag({ x, y })
  }
}

export default () => {
  const drag = new Drag()

  return { drag }
}

然后是 preload.ts

contextBridge.exposeInMainWorld('api', {
  drag: (opt: { x: number; y: number }) => {
    ipcRenderer.invoke('drag', opt)
  }
})

主进程 main.ts

import { BrowserWindow, ipcMain } from 'electron'
export default (win: BrowserWindow) => {
  ipcMain.handle('drag', (_event, opt: { x: number; y: number }) => {
    const [x, y] = win.getPosition()
    win.setPosition(x + opt.x, y + opt.y)
  })
}

无边框圆角

下面介绍无边框圆角窗口的定义,但是由于不同系统的限制,苹果系统表现良好,有些系统默认不会有效果。

index.html 模板

<main>
  <video></video>
</main>

css 样式定义

main {
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  border: solid 5px #9b59b6;
  border-radius: 50%;
  overflow: hidden;
  video {
    object-fit: cover;
  }
}

main.js 主进程窗口定义

const mainWindow = new BrowserWindow({
 	width: 300,
  height: 300,
  minWidth: 200,
  minHeight: 200,
  frame:false,
  transparent: true,
  alwaysOnTop: true,
  ...
})

隐藏鼠标

主要是通过 webContents.insertCSS 添加样式来完成,下面是定义主进程事件,当切换全屏时对 video标签隐藏鼠标

let cssKey = ''
ipcMain.on('toggleFullScreen', async (event: IpcMainEvent) => {
  const win = BrowserWindow.fromWebContents(event.sender)!
  if (win.isFullScreen()) {
    win.webContents.removeInsertedCSS(cssKey)
    win.setFullScreen(false)
    cssKey = ''
  } else {
    cssKey = await win.webContents.insertCSS(
      'video { cursor: none !important;-webkit-app-region: no-drag }',
    )
    win.setFullScreen(true)
  }
})

Sqlite

启动软件时报以下错误

App threw an error during load
SqliteError: unable to open database file
    at new Database (/Users/bm/code/tool/node_modules/.pnpm/better-sqlite3@11.1.1/node_modules/better-sqlite3/lib/database.js:69:26)

一般情况下是你启动了多次应用,造成 sqlite 数据库被使用,所以退出同名应用,只启动一个,这种情况一般出现在开发项目时。

编译后打开新窗口

有这个场景,我们想在打开新窗口时,显示 react 或 vue 的指定路由,但是编译后加载的是静态的 html 文件,不能直接操作。我们可以使用 node:url 模块来加载文件并指定 hash,解决这个问题。

import url from 'node:url'
...
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
  mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/#config')
} else {
  mainWindow.loadURL(
    url.format({
    	//编译后的文件
      pathname: join(__dirname, '../renderer/index.html'),
      //协议
      protocol: 'file',
      //protocol 后面需要两个/
      slashes: true,
      //hash 的值
      hash: 'config'
    })
  )
}

鼠标穿透

思路就是鼠标进入 body 时让鼠标可以穿透到 electron 软件的后面程序,鼠标离开 body 元素时让鼠标不穿透,可以点击我们的 electron 软件。

创建hook 用于定义鼠标穿透事件

//鼠标穿透处理
export const useIgnoreMouseEvents = () => {
  document.body?.addEventListener('mouseover', (e: MouseEvent) => {
    if (e.target === document.body) {
      window.api.setIgnoreMouseEvents(true, { forward: true })
    }
  })
  document.body?.addEventListener('mouseout', () => {
    window.api.setIgnoreMouseEvents(false)
  })
}

hook 中的 setIgnoreMouseEvents 是主进程的穿透事件

ipcMain.on(
  'setIgnoreMouseEvents',
  (event: IpcMainEvent, ignore: boolean, options?: { forward: boolean }) => {
    getWindowByEvent(event).setIgnoreMouseEvents(ignore, options)
  }
)

然后在组件中使用该 hooks

const { setIgnoreMouseEvents } = useIgnoreMouseEvents()
setIgnoreMouseEvents()