electron快速上手

electron 起手式

当前版本为官网最新版本 V25.3.0,实践中发现在更改镜像的情况下使用 npmpnpm下载 electron 都会等待时间过长的问题,推荐使用 yarn

yarn init 
yarn add nodemon electron -D

配置 package.json

{
      "name": "demo",
      "version": "1.0.0",
      "description": "",
      "main": "main.js", /*electron 主进程文件*/
      "scripts": {
        "dev": "nodemon --exec electron ."
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "electron": "^25.3.0",
        "nodemon": "^3.0.1"
      }
}

创建 main.js

const {app,BrowserWindow} = require("electron");
const path = require("path");
const createWindows = ()=>{
  const mainWindow = new BrowserWindow({
    width:200,
    height:200,
    alwaysOnTop:false, /*是否置顶*/
    x:0, /*窗口的x轴位置*/
    y:100, /*窗口的y轴位置*/
    frame:false, /*隐藏标题栏*/
    transparent:true, /*背景透明*/
  })
   /* 网页套壳*/
   /* mainWindow.loadURL("http://www.gjweb.top")*/
   /* 解析项目文件*/
  mainWindow.loadFile(path.resolve(__dirname,"index.html"));
   /* 控制窗口缩放比例 1:1*/
  mainWindow.setAspectRatio(1);
   /* 自动打开开发者工具*/
  mainWindow.webContents.openDevTools();
}
app.whenReady().then(()=>{
  createWindows(); /* 创建窗口*/
})

创建 index.html 视图

<!DOCTYPE html>
<html lang="en">
<head>
 <!-- 安全策略 -- 只有自身可以调用主进程对系统进行操作 技术栈CSP -->
 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'" />
 <meta charset="UTF-8">
 <title>客户端</title>
 <style>
   html{
     /* 点击DOM可拖动 */
     -webkit-app-region:drag
  }
   textarea{
     /* 文本域禁止拖动 */
     -webkit-app-region:no-drag
  }
 </style>
</head>
<body>
   <h1>hello electron</h1>
</body>
   <!--创建渲染进程-->
<script src="./render.js"></script>
</html>

创建渲染进行 render.js

console.log("渲染线程已创建")

总结: electron 中 main.js 作为主线程主要对系统相关操作(只能有一个主线程), index.html 页面(可以有多个页面分到不同的窗口和路由), render.js 渲染线程可以操作浏览器对象(可以有多个渲染进程)


主进程通信

主要思路: 主进程(main.js) —> 预加载处理器( preload.js)—> 渲染进程 (render.js) 反向同理

考虑安全原因 preload.js 只能使用部分的 node 和 electron API; 需要在 main.js 中配置给相应的权限。

主进程 和 渲染进程 也可直接通信(处于安全考虑不推荐)

配置 main.js

const { ipcMain, ipcRenderer } = require("electron");
const mainWindow = new BrowserWindow({
    ...
    /**指定预加载脚本,用于主线程和渲染线程通信 */
    webPreferences:{
      preload:path.resolve(__dirname,'preload.js'),
      nodeIntegration:true, /* 同意 预处理进程使用 node 的模块*/
    }
  })
 
 /**主进程 和 预加载文件通信 */
ipcMain.on("saveData",()=>{
  console.log("触发了saveData");
})

配置预处理器中 preload.js

const { ipcMain, ipcRenderer } = require("electron");
const { contextBridge } = require("electron");
ipcRenderer.send("saveData") /* 预处理器主动像主进程通信*/
​
/**
 * 创建供渲染进程访问的函数 
 * 使用渲染进程可以直接通过 window 获取 api 里面的函数方法
 * */
contextBridge.exposeInMainWorld('api',{
  getSaveData:()=>{
    ipcRenderer.send("saveData")
  }
})
​
/*预处理器操作渲染进程进行dom操作*/
window.addEventListener('DOMContentLoaded',()=>{
  for(const app of ['chrome','electron','node']){
    console.log(app);
    const el = document.querySelector(`#${app}`)
    el.innerHTML = `${app}  版本号 ${process.versions[app]}`
  } 
})

在渲染进程中 render.js

console.log(window); /* window示例可以打印出 api 对象的方法*/
window.api.getSaveData() /* 通过预处理器通知主进程*/

总结: preload 预处理器进程是 主进程 和 渲染进程 的桥梁, preload 既可以操作部分的 node API 也可以调用 DOM 内容。


主进程主动向渲染进程通信

main.js

const mainWindow = new BrowserWindow({
  ...
   /**指定预加载脚本,用于主线程和渲染线程通信 */
   webPreferences:{
     preload:path.resolve(__dirname,'preload.js'),
     nodeIntegration:true, // 同意 预处理进程使用 node 的模块
  }
})
mainWindow.loadFile(path.resolve(__dirname,"index.html"))
/*mainWindow 表示当前窗口的对象,通过 webContents 可以获取渲染进程中的对象(获取通信的方式也是通过预处理器来传递的,所有 webContents 也可以调用到 preload 中的内容)*/
mainWindow.webContents.send("toRender",100);

preload.js

const { contextBridge,ipcRenderer } = require("electron");
/**
* 创建供渲染进程访问的函数
* 使用渲染进程可以直接通过 window 获取 api 里面的函数方法
* */
contextBridge.exposeInMainWorld('api',{
 toRender:(callback)=>{
   ipcRenderer.on('test', callback)
}
})

render.js

  const testFun = (event,value)=>{
   testContainer.innerHTML = Number(testContainer.innerHTML) + value;
}
 window.api.toRender(testFun)

webContents 问题

webContents 和 fromWebContents 有啥区别

webContent 是 electron 中的一个类,用于控制和操作渲染进程的内容,常用来与渲染进程通信、导航、执行 JS 代码、注入 css等。可以通过BrowserWindow实例的webContents 对象来访问当前窗口对象,例如 mainWindow.webContents 将返回 mainWindow 的窗口对象

formWebContents 是 BrowserWindow 的静态方法, 接受一个 webContents 对象为参数, 返回与 webContents 对象关联的 BrowserWindow 实例。 例如 BrowserWindow.fromWebContents(webContents)将返回与给定webContents对象关联的BrowserWindow实例。这个方法通常用于在主进程中查找与特定webContents对象相关联的窗口。

总结 fromWebContents 方法用于在主进程中查找指定的 webContents 对象相关的窗口;webContents 用于控制操作渲染进程对象。


双向通信

基础版

主进程 main.js

const createWindow = ()=>{
  const mainWindow = new BrowserWindow({
   ...
    webPreferences:{
      preload:path.resolve(__dirname,'preload.js'),
      nodeIntegration:true, /* 同意 预处理进程使用 node 的模块*/
    }
  })
​
  mainWindow.loadFile(path.relative(__dirname,"index.html"))
}
app.whenReady().then(()=>{
  createWindow()
})
​
ipcMain.on("mainData",(event,value)=>{
  console.log("mainData",value);
  /*获取当前窗口示例,向渲染进程通信*/
  BrowserWindow.fromWebContents(event.sender).send("toRender","向渲染进程返回消息")
})
​

预处理器 preload.js

const {ipcRenderer,contextBridge} = require("electron");
/**
* 创建供渲染进程访问的函数
* 使用渲染进程可以直接通过 window 获取 api 里面的函数方法
* */
contextBridge.exposeInMainWorld("api",{
 getMainData:()=>{
   ipcRenderer.send("mainData","向主进程发送信息")
}
})
/*接受主进程的信息*/
ipcRenderer.on("toRender",(event,value)=>{
 console.log(value)
})

invoke双向通信

main.js

ipcMain.handle("test",(event,value)=>{
   console.log(value);
   rteurn "invoke 主进程向渲染进程通信"
})

preload.js

contextBridge.exposeInMainWorld("api",{
    setTest:(value)=>{
        return ipcRenderer.invoke("test",value); /* invoke 返回 promise 对象*/
    }
})

render.js

 const result = await windos.api.setTest("渲染进程向主进程通信")
console.log(result)

进程隔离与沙盒模式

进程隔离

在 electron 中预处理器会默认隔离主进程和渲染进程,例如

contextBridge.exposeInMainWorld("api",{
   test:()=>{
       console.log("this is test")
  }
})

如上面代码 在 preload 预处理器文件中需要这样注册后才能在 渲染进程中 通过 window.api 进行调用, 如果想在 preload 文件中直接通过增加 window 示例的方式,需要接触默认隔离,解除隔离后渲染进程 和 预处理进程 都可以使用 node 的全部模块

配置方式如下:main.js 文件

const createWindow = ()=>{
  const mainWindow = new BrowserWindow({
   ...
    webPreferences:{
      preload:path.resolve(__dirname,'preload.js'),
      nodeIntegration:true, /* 同意 预处理进程使用 node 的模块*/
      contextIsolation:false, /* 管理隔离状态*/
    }
  })
  mainWindow.loadFile(path.relative(__dirname,"index.html"))
}

如上配置后 preload 预处理文件可以直接对 window 实例添加方法,共渲染进程使用,代码如下

/* preload.js*/
window.api = {
    test:()=>{
        console.log("this is test")
    }
}
​
/* render.js 渲染进程*/
window.api.test();
/* main.js 主进程也可通过 global 对象调用*/
global.api.test()

沙盒模式

开启沙河模式后 只有 预处理文件 (preload.js)可以用使用 node 的全部模块(相比接触进程隔离更加安全)

配置窗口对象

const createWindow = ()=>{
  const mainWindow = new BrowserWindow({
    ...
    webPreferences:{
      preload:path.resolve(__dirname,'preload.js'),
      nodeIntegration:true,
      contextIsolation:false, /*开启沙盒模式*/
    }
  })
  ...
}

动态窗口尺寸

mainWidow.center() /*窗口居中*/
mainWidow.setBounds({
    width:
    height:
    x:
    y:
},true) /*开启动画*/

菜单管理

创建 menu.js 统一管理菜单

值得注意的在 mac 平台中 第一个菜单为软件本身的示例,需要单独兼容

const {Menu} = require("electron");
const createMenu = ()=>{
  const menu = [
    {
      label: 'File', /*一级菜单 名称*/
      submenu: [ /* 子菜单对象*/
        { 
          label: 'New',  /*二级菜单名称*/
          accelerator: 'CmdOrCtrl+N',  /* 快捷键*/
          click: () => { /* 处理点击事件 */ }  /*自定义事件*/
        },
        { 
          label: 'Open', 
          accelerator: 'CmdOrCtrl+O', 
          click: () => { /* 处理点击事件 */ } 
        },
        { 
          type: 'separator'  /*分割线*/
        },
        { 
          label: 'Quit', 
          accelerator: 'CmdOrCtrl+Q', 
          click: () => { /* 处理点击事件 */ } 
        }
      ]
    },
    /*其他菜单项...*/
  ];
  const applicationMenu = Menu.buildFromTemplate(menu);
  Menu.setApplicationMenu(applicationMenu);
}
​
module.exports = createMenu;

在 main.js 中引用创建菜单

const {app,BrowserWindow, ipcMain} = require("electron");
const createMenu = require("menu.js")
app.whenReady().then(()=>{
    ...
    createMenu() /*创建菜单*/
})

右键菜单

在预加载进程中监听 dom 的右键事件

/*preload.js*/
const {ipcRenderer} = require("electron");
/* 全局监听右键点击事件*/
window.addEventListener("contextmenu",()=>{
  console.log("右键测试");
  ipcRenderer.send("handelContextmenu")
})

在主进程中通信中创建右键菜单

/**创建右键菜单 */
const {ipcMain, Menu, BrowserWindow} = require('electron')
​
ipcMain.on("handelContextmenu",(event)=>{
  const template = [
    {
      label:"退出",
      click:()=>console.log("退出应用")
    }
    { type: 'separator' }, /*菜单分割线*/
  ]
  const menu = Menu.buildFromTemplate(template)
  menu.popup(BrowserWindow.fromWebContents(event.sender))
})

dialog 弹窗

在只能在主进程中使用, 详细配置见官方文档

信息窗

const {dialog} = require("electron")
/*
* result 返回一个对象 
*  response 表示 buttons 中点击的下标
*  checkboxChecked 表示 checkboxLabel 属性是否被勾选
*/
const result = await dialog.showMessageBox({
    title:'提示',
    detail:'确认要退出吗',
    buttons:["取消","确定"],
    cancelId:0, /*按下 esc 时走 取消 选项*/
    checkboxLabel:"记住我的选择"
})

错误弹窗

dialog.showErrorBox("温馨提示","下次将不在提示退出弹窗")

选择文件

ipcMain.handle("checkFile",async (event)=>{
 /*选择文件,返回选中文件的绝对路径*/
 const {filePaths} = await dialog.showOpenDialog({
   title:"选择文件",
   filters:[{name:"files"}],
   properties:["openFile","multiSelections"]
})
 return filePaths
})

保存文件

/*返回保存文件的路径*/
const {filePath} = await dialog.showSaveDialog({
    title:'保存文件'
})
/*使用 node fs 模块将一个文件内容写入到另一个文件中*/
fs.readFile(fliePath,'utf8',(err,data)=>{
    if(err){throw err};
    /*写入目标文件*/
    fs.writeFile(filePath,data,"utf8",(err)=>{
      if(err){throw err}
      dialog.showMessageBox({
        title:'提示信息',
        detail:"文件成功写入"
      })
    })
  })

托盘图标设置

规范性: mac 系统用中图标为黑白色, windows 系统可以为彩色; 尺寸: 32*32 144dpi

/*模块抽离 使用 createTray.js*/
const {Menu,Tray} = require("electron");
cons {resolve} = require("path");
const createTray = ()=>{
  /*设置托盘图标的图片 需要放在 electron 项目的 resources 文件下面*/
  const tray = Tray(resolve(__dirname,'resources文件夹下的路径'))
  /* 设置托盘图标右键的菜单,菜单属性同 Menu 配置一致*/
  const contextMenu = Menu.buildFromTemplate([{ label: '退出', role: 'quit' }])
  /* 鼠标悬停到托盘图标时的提示信息*/
  tray.setToolTip('hello electron')
  tray.setContextMenu(contextMenu)
}
module.exports = {createTray}

在 mian.js 中调用


const {app} = require("electron");
const {crateTray} = require("createTray.js")

app.whenReady().then(()=>{
   createTray()
})

状态栏图标设置

/*windos中在 BrowserWindow 示例中配置*/
new BrowserWindow({
    skipTaskbar:flase,/* 隐藏托盘图标*/
})
/*在 mac 在 whenReady 中配置*/
app.whenReady().then(()=>{
    app.dock.hide() /* 隐藏托盘图标*/
})

右上角关闭、缩小、窗口

创建 window.ts 文件 抽离模块

import { BrowserWindow, ipcMain } from "electron";

/**关闭窗口 */
ipcMain.on("handelClose",()=>{
  const focusedWindow = BrowserWindow.getFocusedWindow();
  if(focusedWindow){
    focusedWindow.hide()
  }
})
/* 最小化窗口 */
ipcMain.on("handelMinimize",()=>{
  const focusedWindow = BrowserWindow.getFocusedWindow();
  if(focusedWindow){
    focusedWindow.minimize()
  }
})
/* 窗口最大化 */
ipcMain.on("handelMaxWindow",()=>{
  const focusedWindow = BrowserWindow.getFocusedWindow();
  if(focusedWindow){
    focusedWindow.isMaximized()?focusedWindow.restore():focusedWindow.maximize();
  }
})
/* 获取窗口大小的状态 */
ipcMain.handle("getWindowStatus",()=>{
  const focusedWindow = BrowserWindow.getFocusedWindow();
  return focusedWindow?.isMaximized()
})

在主进程引入 index.ts

import "./window"

electron-vite 构建打包

构建

pnpm install vite -g
pnpm create @quick-start/electron
✔ Project name: … <electron-app>
✔ Select a framework: › vue
✔ Add TypeScript? … No / Yes
✔ Add Electron updater plugin? … No / Yes
✔ Enable Electron download mirror proxy? … No / Yes

Scaffolding project in ./<electron-app>...
Done.

配置热更新

/*package.json*/
{
   "scripts":{
       "dev": "electron-vite dev --watch",
  }
}

指定的目录功能

  • build 文件夹可存放桌面图标
  • resources 文件夹可存放托盘图标
  • src 文件包括 main 主线程文件夹 preload 预加载文件夹 和 renderer 渲染文件夹(该文件夹主要存放vue或react等)

配置桌面图标

在 build 文件存放 icon.png 图片,作为桌面图标 同时将原有的三个文件更改文件名

mac 系统要求 尺寸:512 * 512

windows 系统要求尺寸不小于: 216*216

electron打包

package.json 中配置

{
   "name":"项目名称",
   "version":"版本号"
}

electron-builder.yml 中配置

appId: com.自定义.自定义
productName: 软件的名称

在 mac 系统中只能打包 mac 版本, 在mac中搭建虚拟机也无法打包出 windows 的版本

pnpm build:mac

在 windows 版本中打包

pnpm build:win
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇