初识electron会遇到的一些问题

430 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天, 点击查看活动详情

1.第一个electron应用

首先打开vsCode终端输入mkdir electron-demo创建文件夹, cd到electron-demo文件夹,创建一个package.json文件,里面输入如下代码:

{
    "name": "electron-demo", // 项目名
    "version": "1.0.0",  // 版本号
    "main": "./src/index.js", // 入口文件
}

项目结构

5f5f6655586aaeae179209d182dcb7a.jpg

安装electron,npm i electron,在index.js里面输入如下代码:

const { app, BrowserWindow } = require('electron')

app.on('ready', () => {
    // 创建BrowserWindow实例
    let mainWindow = new BrowserWindow({
        // 设置窗口大小,最小minWidth minHeight 最大maxWidth maxHeight
        width: 800,
        height: 600,
        // x,y控制窗口打开的位置
        x: 100,
        y: 100,
    })
    // 加载本地index.html文件
    mainWindow.loadFile('./src/views/index.html')
    mainWindow.on('close', () => { // 页面关闭的时候清除该变量,防止内存泄漏
        mainWindow = null
    })
})

index.html代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>我是第一个electron应用</h1>
</body>
</html>

在终端输入npm init,操作如下

4a4d83ef08ea7c73052a6e94741852a.jpg

package.json第一个应用完整代码如下

{
  "name": "electron-demo",
  "version": "1.0.0",
  "main": "./src/index.js",
  "dependencies": {
    "electron": "^19.0.9"
  },
  "devDependencies": {},
  "scripts": {
    "test": "electron ." // 启动程序
  },
  "author": "",
  "license": "ISC",
  "description": ""
}

终端输入npm run test或者electron .即可启动第一个electron,运行成功图片如下 4a34b2f065c02435269dcac47e6cb37.jpg

2.访问Node.js环境

项目结构如下:

0d68c7a9356a336b6d3fd2ae9f37270.jpg

index.html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>我是第一个electron应用</h1>
    <button id="btn">新建窗口</button>
    <script src="../child.js"></script>
</body>
</html>

index.js代码如下

const { app, BrowserWindow } = require('electron')

app.on('ready', () => {
    // 加载本地index.html文件
    let mainWindow = new BrowserWindow({
        // 设置窗口大小,minWidth maxWidth minHeight maxHeight
        width: 800,
        height: 600,
        // x,y控制窗口打开的位置
        x: 100,
        y: 100,
    })
    mainWindow.webContents.openDevTools() // 打开控制台
    // 加载本地index.html文件
    mainWindow.loadFile('./src/views/index.html')
    mainWindow.on('close', () => {
        mainWindow = null
    })
    
    // 默认最大化
    // let mainWidth = new BrowserWindow({
    //     show: false
    // })
    // mainWidth.maximize()
    // mainWidth.show()
    
    // 默认全屏
    // let mainWidth = new BrowserWindow({ fullscreen: true})
})

child.html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body{
            background-color: pink;
        }
    </style>
</head>
<body>
    <h1>我是新建的窗口</h1>
</body>
</html>

child.js代码如下

const btn = document.getElementById('btn')
console.log(123);
const BrowserWindow = require('electron').remote.BrowserWindow
btn.onclick = function() {
    let win = new BrowserWindow({
        width: 300,
        height: 200
    })
    win.loadFile('./src/views/child.html')
    win.on('close', () => {
        win = null
    })
}

这里我们会发现报ReferenceError: require is not defined, b9a69cc0c2a549cb1781aea82df2154.jpg

我们需要在index.js文件中new BrowserWindow里面加入webPreferences: { nodeIntegration: true },但是添加之后还是报require is not defined,这是因为在electron12版本中默认contextIsolation: true, 我们只需要在原基础上在添加如下代码

contextIsolation: false

新index.js代码如下

const { app, BrowserWindow } = require('electron')

app.on('ready', () => {
    // 加载本地index.html文件
    let mainWindow = new BrowserWindow({
        // 设置窗口大小,minWidth maxWidth minHeight maxHeight
        width: 800,
        height: 600,
        // x,y控制窗口打开的位置
        x: 100,
        y: 100,
        webPreferences: {
            nodeIntegration: true, // 允许在这个渲染进程调用node.js
            contextIsolation: false, // 解决require is not defined
        }
    })
    mainWindow.webContents.openDevTools() // 打开控制台
    // 加载本地index.html文件
    mainWindow.loadFile('./src/views/index.html')
    mainWindow.on('close', () => {
        mainWindow = null
    })
})

但是我们会发现确实不报require is not defined了,但是开始报Cannot read properties of undefined (reading 'BrowserWindow'), 这是因为remote在electron14的时候废弃了remote模块,所以需要我们自己安装@electron/remote包。如果你想要了解@electron/remote可以点击这里。

npm install @electron/remote

这里我们在index.js中添加如下代码

webPreferences: {
    nodeIntegration: true, // 允许在这个渲染进程调用node.js
    contextIsolation: false, // 解决require is not defined
    enableRemoteModule:true // 开启remote
}

在主进程中进行初始化

require('@electron/remote/main').initialize()
require('@electron/remote/main').enable(mainWindow.webContents)

2.在child.js添加如下代码, 将const BrowserWindow = require('electron').remote.BrowserWindow 修改为const BrowserWindow = require('@electron/remote').BrowserWindow,在new BrowserWindow中添加

webPreferences: {
    nodeIntegration: true, 
    contextIsolation: false
}

,修改完后就可以点击新建窗口创建成功窗口啦(^-^)。 运行成功图片如下

8dc365b89e5aeb659fc7be40145980e.png

3.自定义菜单

项目结构

d4d040dd27d42d66600c42620cec866.jpg

在index.js文件中添加require('./menu.js'),index.js代码如下

const { app, BrowserWindow } = require('electron')
// 导入自定义菜单menu.js
require('./menu.js')

app.on('ready', () => {
    // 加载本地index.html文件
    let mainWindow = new BrowserWindow({
        // 设置窗口大小,minWidth maxWidth minHeight maxHeight
        width: 800,
        height: 600,
        // x,y控制窗口打开的位置
        x: 100,
        y: 100,
        webPreferences: {
            nodeIntegration: true, // 允许在这个渲染进程调用node.js
            contextIsolation: false, // 解决require is not defined
            enableRemoteModule:true // 开启remote
        }
    })
    // 解决报Cannot read properties of undefined (reading 'BrowserWindow'
    require('@electron/remote/main').initialize()
    require('@electron/remote/main').enable(mainWindow.webContents)

    mainWindow.webContents.openDevTools() // 打开控制台
    // 加载本地index.html文件
    mainWindow.loadFile('./src/views/index.html')
    mainWindow.on('close', () => {
        mainWindow = null
    })

    // 默认最大化
    // let mainWidth = new BrowserWindow({
    //     show: false
    // })
    // mainWidth.maximize()
    // mainWidth.show()
    
    // 默认全屏
    // let mainWidth = new BrowserWindow({ fullscreen: true})
})

lately.html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我是新建窗口</title>
</head>
<body>
    <h1>我是新建窗口</h1>
</body>
</html>

menu.js代码如下

const { Menu, BrowserWindow } = require('electron')

// 创建菜单模板,数组里的每一个对象都是一个菜单
const template = [
    {
        label: '文件',
        // submenu 代表下一级菜单
        submenu: [
            {
                label: '新建窗口',
                accelerator: 'ctrl+n', // 快捷键
                click: () => {
                    let win = new BrowserWindow({ // 添加点击事件
                        width: 400,
                        height: 400
                    })
                    win.loadFile('./src/views/lately.html')
                    win.on('close', () => { // 监听是否关闭窗口
                        win = null
                    })
                }
            },
            { label: '打开最近文件' },
            {
                label: '新建文件',
                click: () => {
                    let win = new BrowserWindow({
                        width: 300,
                        height: 150
                    })
                    win.on('close', () => {
                        win = null
                    })
                } 
            }
        ]
    },
    {
        label: '编辑',
        submenu: [
            { label: "撤销", accelerator: "ctrl+z" },
            {
                label: "查找",
                accelerator: "ctrl+f",
                submenu: [
                    { label: "在文件中查找", accelerator: "ctrl+shift+f" }
                ]
            },
            { label: "恢复" }
        ]
    },
    {
        label: "选择",
        submenu: [
            { 
                label: "全选",
                accelerator: "ctrl+a"
            }
        ]
    }
]
// 从模板中创建菜单
const menu = Menu.buildFromTemplate(template)
// 设置为应用程序菜单
Menu.setApplicationMenu(menu)

运行结果如图

f1fb8ea64f4a623cd1dd5fdd38a163b.jpg

4.electron应用设置无边框

  • 在创建窗口的时候设置无边框,这里我们可以看到菜单也消失了。在index.js中输入如下代码
new BrowserWindow({
    frame: false, // 让桌面应用无边框,菜单栏也消失了
})

其实菜单栏还在,我们可以通过快捷键调出菜单,也可以直接删除菜单mainWidth.removeMenu()。完整index.js代码如下

const { app, BrowserWindow } = require('electron')
// 导入自定义菜单menu.js
require('./menu.js')

app.on('ready', () => {
    // 加载本地index.html文件
    let mainWindow = new BrowserWindow({
        // frame: false, // 让桌面应用无边框,菜单栏也消失了
        // 设置窗口大小,minWidth maxWidth minHeight maxHeight
        width: 800,
        height: 600,
        // x,y控制窗口打开的位置
        x: 100,
        y: 100,
        webPreferences: {
            nodeIntegration: true, // 允许在这个渲染进程调用node.js
            contextIsolation: false, // 解决require is not defined
            enableRemoteModule:true // 开启remote
        }
    })
    // 解决报Cannot read properties of undefined (reading 'BrowserWindow'
    require('@electron/remote/main').initialize()
    require('@electron/remote/main').enable(mainWindow.webContents)

    mainWindow.removeMenu() // 删除菜单
    mainWindow.webContents.openDevTools() // 打开控制台
    // 加载本地index.html文件
    mainWindow.loadFile('./src/views/index.html')
    mainWindow.on('close', () => {
        mainWindow = null
    })

    // 默认最大化
    // let mainWidth = new BrowserWindow({
    //     show: false
    // })
    // mainWidth.maximize()
    // mainWidth.show()

    // 默认全屏
    // let mainWidth = new BrowserWindow({ fullscreen: true})
})
  • 我们会发现没有边框的时候,我们不能拖动窗口,在css中我们可以设置哪个可以进行拖拽/禁止拖拽。
    • 例如body{ -webkit-app-region: drag | no-drag; }

5.系统托盘

  • 我们在使用电脑的时候,可以看到一些,我们不想在电脑栏看见的应用,又不想关闭,就可以把它设置到系统托盘中

eb876a3fc162255c59be2b631e34baf.jpg

如果你想要了解更多,可以点击这里。

  • 当然,我们可以在主进程index.js中添加系统托盘,添加左键事件,右键事件带菜单等。注意作为系统托盘图标的图片不能是改了后缀名的。例如:bg.png的图片我们改了后缀名改成了bg.jpg,然后我们使用bg.jpg的话,托盘图标是不会显示的哟,只有bg.png原来的这种才会显示。
const { app, BrowserWindow, Tray, nativeImage, Menu } = require('electron')
const path = require('path')

// 导入自定义菜单menu.js
require('./menu.js')
let mainWindow
app.on('ready', () => {
    // 加载本地index.html文件
    mainWindow = new BrowserWindow({
        // frame: false, // 让桌面应用无边框,菜单栏也消失了
        // 设置窗口大小,minWidth maxWidth minHeight maxHeight
        width: 800,
        height: 600,
        // x,y控制窗口打开的位置
        x: 100,
        y: 100,
        webPreferences: {
            nodeIntegration: true, // 允许在这个渲染进程调用node.js
            contextIsolation: false, // 解决require is not defined
            enableRemoteModule:true // 开启remote
        }
    })
    // 创建icon图标
    const icon = nativeImage.createFromPath(
        path.join(__dirname,'/images/bg.png')
    )
    // 实例化一个托盘对象,传入的是托盘的图标
    let tray = new Tray(icon)
    // 移动到托盘上的提示
    tray.setToolTip('electron demo is running')
    // 设置title
    tray.setTitle('electron demo')
    // 监听托盘右键事件
    tray.on('right-click', () => {
        // 右键菜单模块
        const template = [
            {
                label: '设置'
            },
            {
                label: '退出',
                click: () => app.quit()
            }
        ]
        // 通过Menu创建菜单
        const menu = Menu.buildFromTemplate(template)
        // 让我们写的托盘右键的菜单代替原来的
        tray.popUpContextMenu(menu)
    })
    //监听点击托盘的事件
    tray.on('click', () => {
        // 这里来控制窗口的显示和隐藏
        if (mainWindow.isVisible()) {
            mainWindow.hide()
        } else {
            mainWindow.show()
        }
    })

    // 解决报Cannot read properties of undefined (reading 'BrowserWindow'
    require('@electron/remote/main').initialize()
    require('@electron/remote/main').enable(mainWindow.webContents)

    // mainWindow.removeMenu() // 删除菜单
    mainWindow.webContents.openDevTools() // 打开控制台
    // 加载本地index.html文件
    mainWindow.loadFile('./src/views/index.html')
    mainWindow.on('close', () => {
        mainWindow = null
        app.quit()
    })

    // 默认最大化
    // let mainWidth = new BrowserWindow({
    //     show: false
    // })
    // mainWidth.maximize()
    // mainWidth.show()

    // 默认全屏
    // let mainWidth = new BrowserWindow({ fullscreen: true})
})

app.on('window-all-closed', () => {
    mainWindow = null
})

6.electron 控制台打印乱码问题?

我们可能在Windows的控制台会出现中文乱码的问题,当我们在Windows的控制台下输入chcp,可以查看到当前字符编码,常见的gb2312的值是936,utf8的值是65001。这种情况下只要对package.json进行设置就能解决。

"test": "chcp 65001 && electron ."

7.如何对项目进行打包?

打包也是必不可少的一步,这里介绍两种比较成熟的打包工具:electron-packagerelectron-builder。这两个工具主要是对其进行配置。

electron-packager

先进行安装

npm install electron-packager --save-dev

安装好之后要配置electron-packager的基本命令,下面为官方文档中的基本格式:

electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]

简要的介绍一下各个参数所代表的意思:

  • sourcedir:项目所在路径
  • appname:应用名称(打包后文件的名称)
  • platform:确定了你要构建哪个平台的应用(Windows、Mac 还是 Linux)
    • platform=win32 代表Windows
    • platform=darwin 代表Mac
    • platform=linux 代表Linux
  • arch:决定了使用 x86 还是 x64 还是两个架构都用
  • optional options:可选选项
  • --out表示打包后生成的文件的目录
  • --app-version表示打包生成文件的版本号
  • --overwrite表示删除原有的打包文件,生成新的打包文件
  • --icon表示打包文件的图标

下面为一个例子,供参考

"scripts": {
    "test": "electron .",
    "build32": "electron-packager ./ 黑洞 --platform=win32 --arch=ia32 --out=./dist --app-version=1.0.0 --overwrite --icon=hd.ico",
    "build": "electron-packager . 黑洞 --platform=win32 --arch=x64 --out=./dist --asar --app-version=1.0.0 --overwrite --icon=hd.ico"
  },

build32打包的为32位,build打包的为64位,打包的文件在dist文件夹下面,如下图

eb16fcf8b486bc5a251248ea6ddd005.jpg

注意哟这里和前面系统托盘图标一样,我们不能使用只是改了后缀名的ico图标哟,如果没有合适的ico图片,可以点击这里。

最后

感谢大家能看到这里,若有问题请指正(^-^)。