Electron 制作自定义浏览器

Electron 是一款基于 Chromium 和 Node.js 的开源框架,支持使用 HTML、CSS、JavaScript 等前端技术开发跨平台桌面应用,一套代码可运行在 Windows、macOS、Linux 系统,采用主进程 + 渲染进程的双进程架构,兼顾原生系统能力与网页端灵活的界面开发,VS Code、钉钉、飞书等知名软件均基于它打造。

1.创建项目

注意:开发环境必须如下:

安装 nodejs 版本 :22.22.3

安装 Git:2.54.0

创建 Electron 项目,并且编写一个简单的入门程序。 到了本节末尾,应该能够在终端开发环境运行一个 Electron 应用。

Electron 应用基于 npm 搭建,以 package.json 文件作为入口点。 首先创建一个文件夹,然后在其中执行 npm init 初始化项目。

复制代码
mkdir my-electron-app && cd my-electron-app
npm init

这条命令会帮您配置 package.json 中的一些字段。 为本教程的目的,有几条规则需要遵循:

  • 入口点 应当是 index.js (您很快就会创建它)
  • authorlicensedescription 可以是任何值,但在稍后的packaging中是必需的。

然后,将 Electron 安装为您项目的 devDependencies,即仅在开发环境需要的额外依赖。

注意:应用需要运行 Electron API,因此这听上去可能有点反直觉。 在底层实现中,Electron 的 JavaScript API 绑定到了一个包含其实现的二进制文件上。 Electron 的打包步骤负责处理对这个二进制文件的捆绑工作,因此无需将它指定为生产依赖。

复制代码
npm install electron --save-dev

在初始化并且安装完 Electron 之后,您的 package.json 应该长下面这样。 文件夹中会出现一个 node_modules 文件夹,其中包含了 Electron 可执行文件;还有一个 package-lock.json 文件,指定了各个依赖的确切版本。

复制代码
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Hello World!",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Jane Doe",
  "license": "MIT",
  "devDependencies": {
    "electron": "^42.3.0"
  }
}

2.运行 Electron 应用

您在 package.json 中指定的 main 文件是 Electron 应用的入口。 这个文件控制 主程序 (main process),它运行在 Node.js 环境里,负责控制您应用的生命周期、显示原生界面、执行特殊操作并管理渲染器进程 (renderer processes),稍后会详细介绍。

在继续编写您的 Electron 应用之前,您将使用一个小小的脚本来确保主进程入口点已经配置正确。 在根目录的 index.js 文件中写一行代码:

复制代码
console.log('Hello from Electron !')

因为 Electron 的主进程就是一个 Node.js 运行时,所以你可以直接用 electron 命令运行任意的 Node.js 代码(甚至还能把它当成 REPL 来用)。 要执行这个脚本,需要在 package.json 的 scripts 字段中添加一个 start 命令,内容为 electron . 。 这个命令会告诉 Electron 在当前目录下寻找主脚本,并以开发模式运行它。

复制代码
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Hello World!",
  "main": "index.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Jane Doe",
  "license": "MIT",
  "devDependencies": {
    "electron": "^42.3.0"
  }
}

执行命令

复制代码
npm run start

您的终端应该会输出 Hello from Electron ! 恭喜,您已经在 Electron 中执行了您的第一行代码! 接下来,您会学习如何用 HTML 创建用户界面,并将它们装载到原生窗口中。

3.网页装载到 BrowserWindow

在 Electron 中,每个窗口展示一个页面,后者可以来自本地的 HTML,也可以来自远程 URL。 在本例中,您将会装载本地的文件。 在您项目的根目录中创建一个 index.html 文件,并写入下面的内容:

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    <h1>Hello from Electron renderer!</h1>
    <p>👋</p>
  </body>
</html>

现在您有了一个网页,您可以将其加载到一个 Electron 的 BrowserWindow 上了。 将 index.js 中的内容替换成下列代码。 我们马上会逐行解释。

javascript 复制代码
const { app, BrowserWindow } = require('electron')

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })

  win.loadFile('index.html')
}

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

在第一行中,我们使用 CommonJS 语法导入了两个 Electron 模块:

  • app,这个模块控制着您应用程序的事件生命周期。
  • BrowserWindow,这个模块创建和管理 app 的窗口。

第二行 createWindow() 函数将您的页面加载到新的 BrowserWindow 实例中:

第三段在应用准备就绪时调用函数,在 Electron 中,只有在 app 模块的 ready 事件(event)触发后才能创建 BrowserWindows 实例。 可以通过使用 app.whenReady() API 来监听此事件,并在其成功后调用 createWindow() 方法。

4.打包应用程序

Electron 的核心模块中没有捆绑任何用于打包或分发文件的工具。 如果您在开发模式下完成了一个 Electron 应用,需要使用额外的工具来打包应用程序 (也称为可分发文件 ) 并分发给用户 。 可分发文件可以是安装程序 (例如 Windows 上的 MSI) 或者绿色软件 (例如 macOS 上的 .app 文件)。

Electron Forge 是一个处理 Electron 应用程序打包与分发的一体化工具。 在工具底层,它将许多现有的 Electron 工具 (例如 @electron/packager@electron/osx-signelectron-winstaller 等) 组合到一起,因此您不必费心处理不同系统的打包工作。

将 Electron Forge 的 CLI 工具包安装到项目的 devDependencies 依赖中,然后使用现成的转化脚本将项目导入至 Electron Forge。

复制代码
npm install --save-dev @electron-forge/cli
npx electron-forge import

转换脚本完成后,Forge 会将一些脚本添加到您的 package.json 文件中。

复制代码
  //...
  "scripts": {
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make"
  },
  //...

您还应该注意到您的 package.json 现在安装了更多的包 在 devDependencies 下,以及一个导出配置的新 forge.config.js 文件 目的。 您应该在预填充的配置中看到多个makers(生成可分发应用程序包的包),每个目标平台一个。

要创建可分发文件,请使用项目中的 make 脚本,该脚本最终运行了 electron-forge make 命令。

复制代码
npm run make

make 命令包含两步:

  1. 它将首先运行 electron-forge package ,把您的应用程序 代码与 Electron 二进制包结合起来。 完成打包的代码将会被生成到一个特定的文件夹中。
  2. 然后它将使用这个文件夹为每个 maker 配置生成一个可分发文件。

在脚本运行后,您应该看到一个 out 文件夹,其中包括可分发文件与一个包含其源码的文件夹。

复制代码
out/
├── out/make/zip/darwin/x64/my-electron-app-darwin-x64-1.0.0.zip
├── ...
└── out/my-electron-app-darwin-x64/my-electron-app.app/Contents/MacOS/my-electron-app

out/make 文件夹中的应用程序应该可以启动了! 现在,您已经创建了你的第一个 Electron 程序。

复制代码
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');

module.exports = {
  packagerConfig: {
    asar: true,
	icon: './dist/favicon.ico', // 图标路径
  },
  rebuildConfig: {}
....

如果需要修改程序图标在 forge.config.js 文件中 packagerConfig 中配置 icon 即可。

javascript 复制代码
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');

module.exports = {
  // 1. 打包基础配置(最常用)
  packagerConfig: {
    asar: true,                     // 打包加密(必开)
    icon: './public/app',           // 图标(不用写后缀,自动匹配 ico/png)
    win32metadata: {                // Windows 安装包信息
      CompanyName: '你的公司名',
      FileDescription: '软件描述',
      ProductName: '软件名称',
    },
  },

  // 2. 编译配置
  rebuildConfig: {},

  // 3. 输出安装包(Windows/macOS/Linux)
  makers: [
    // Windows 最常用:exe 安装包
    {
      name: '@electron-forge/maker-squirrel',
      config: {
        name: 'my_electron_app',
        setupIcon: './public/app.ico', // 安装包图标
      },
    },
    // Windows 绿色免安装 zip
    {
      name: '@electron-forge/maker-zip',
      platforms: ['win32'],
    },
  ],

  // 4. 插件(必配)
  plugins: [
    {
      name: '@electron-forge/plugin-auto-unpack-natives',
      config: {},
    },
    // 安全加固(新版 Electron 必须)
    new FusesPlugin({
      version: FuseVersion.V1,
      [FuseV1Options.RunAsNode]: false,
      [FuseV1Options.EnableCookieEncryption]: true,
      [FuseV1Options.OnlyLoadAppFromAsar]: true,
    }),
  ],
};

5.常见配置

1.去除默认标题栏

让我们先配置一个有原生窗口控件和隐藏标题栏的窗口。 要移除默认标题栏,将 BrowserWindow 构造函数中的 BaseWindowContructorOptions titleBarStyle 参数设置为 'hidden'。

javascript 复制代码
const { app, BrowserWindow } = require('electron')

function createWindow () {
  const win = new BrowserWindow({
    // remove the default titlebar
    titleBarStyle: 'hidden'
  })
  win.loadURL('https://example.com')
}

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

2.全屏幕+覆盖任务栏

复制代码
  const win = new BrowserWindow({
    // 无边框 + 覆盖任务栏 + 全屏
    fullscreen: true,        // ✅ 全屏(覆盖任务栏)
    titleBarStyle: 'hidden' // 标题栏彻底隐藏
  })

3.通过网页内容拖拽浏览器

默认情况下,使用操作系统窗口外框提供的标题栏可以拖拽窗口。 移除默认标题栏的应用需要使用 app-region CSS 属性来定义可以用于拖拽窗口的指定区域。 设置 app-region: drag 会将一块矩形区域标记为可拖拽。

必须要指出的是,可拖拽区域会忽略所有的指针事件。 例如,与可拖拽区域重叠的按钮元素将不会在重叠区域内产生鼠标点击或者鼠标进入/退出事件。 设置 app-region: no-drag 会将一块矩形区域排除出可拖拽区域,从而重新启用指针事件。

要让整个窗口可拖拽,你可以向 body 的样式里添加 app-region: drag

复制代码
body {
  app-region: drag;
}

请注意,如果您使整个窗口都可拖拽,则必须将其中的按钮标记为不可拖拽,否则用户将无法点击它们:

复制代码
button {
  app-region: no-drag;
}

如果你只把自定义标题栏设置为可拖拽,你还需要设置标题栏里所有的按钮为不可拖拽。

6.调用浏览器内置的自定义事件

降低安全配置:仅适合本地开发、个人工具、完全可信的内部页面。

nodeIntegration: true:网页可直接调用 Node.js API(如 fs、child_process),一旦有 XSS 漏洞,恶意代码可读写文件、执行系统命令。

contextIsolation: false:关闭上下文隔离,渲染进程与 Node 上下文合并,全局对象易被篡改。

javascript 复制代码
const { app, BrowserWindow, ipcMain } = require('electron')

const createWindow = () => {
  const win = new BrowserWindow({
    titleBarStyle: 'hidden',
    fullscreen: true,
    webPreferences: {
      // 👇 关键:关闭安全隔离,允许网页直接用 Node/ipcRenderer
      nodeIntegration: true,
      contextIsolation: false
    }
  })
  win.loadFile('dist/index.html')
}

// 定义自定义事件
ipcMain.on('custom-event', (event, data) => {
  console.log('网页调用自定义事件,数据:', data)
  if (data === 'close') app.quit()
})

app.whenReady().then(createWindow)

在HTML页面直接引入 ipcRenderer

html 复制代码
<!DOCTYPE html>
<html>
<body>
  <h1>Electron 无 preload 调用</h1>
  <button onclick="sendToMain()">调用主进程事件</button>
  <button onclick="closeApp()">关闭应用</button>

  <script>
    // 直接引入 ipcRenderer(无 preload)
    const { ipcRenderer } = require('electron')

    function sendToMain() {
      ipcRenderer.send('custom-event', 'Hello from HTML')
      alert('已发送消息到主进程')
    }

    function closeApp() {
      ipcRenderer.send('custom-event', 'close')
    }
  </script>
</body>
</html>

案例,文件读取

javascript 复制代码
const { app, BrowserWindow, ipcMain } = require('electron')

function createWindow() {
  const win = new BrowserWindow({
    fullscreen: true,
    frame: false,
    webPreferences: {
      // 关闭安全隔离(允许网页直接用 Node)
      nodeIntegration: true,
      contextIsolation: false,
    }
  })

  win.loadFile('index.html')
}

app.whenReady().then(createWindow)

index.html(网页直接读取本地文件)

html 复制代码
<!DOCTYPE html>
<html>
<body>
  <h1>Electron 直接读取本地文件</h1>
  <button onclick="readFile()">读取本地文件 test.txt</button>
  <pre id="content"></pre>

  <script>
    // 直接使用 Node.js 的 fs 模块!!
    const fs = require('fs')
    const path = require('path')

    function readFile() {
      // 读取项目目录下的 test.txt
      const filePath = path.join(__dirname, 'test.txt')

      fs.readFile(filePath, 'utf-8', (err, data) => {
        if (err) {
          document.getElementById('content').innerText = '读取失败:' + err
          return
        }
        // 显示文件内容
        document.getElementById('content').innerText = data
      })
    }
  </script>
</body>
</html>

案例:html 控制开发者工具调试窗口

html 复制代码
<button onclick="openDevTools()">打开开发者工具</button>

<script>

    const {  ipcRenderer } = require('electron')

    // 🔥 打开/切换开发者工具
    function openDevTools() {
        ipcRenderer.send('open-devtools')
    }
</script>

index.js

javascript 复制代码
const { app, BrowserWindow,ipcMain  } = require('electron')

const createWindow = () => {
  const win = new BrowserWindow({


    width: 920,
    height: 580,
    webPreferences: {
      nodeIntegration: true,      // 允许网页用Node
      contextIsolation: false     // 关闭安全隔离
    }
  })

  win.loadFile('dist/index.html')
  // 🔥 接收网页消息,打开开发者工具
  ipcMain.on('open-devtools', () => {
    win.webContents.toggleDevTools()
  })

}

app.whenReady().then(() => {
  createWindow()
})
相关推荐
程序二次开发13 小时前
wordpress 文章页,文章分类,单页,woocommerc 产品页,分类页添加.html后缀
大数据·前端·html·php
CodeSheep13 小时前
苦撑13年,创始人离职出走,拉勾终究还是倒下了…
前端·后端·程序员
a11177613 小时前
html制作的PPT(各种风格)提示词
前端·开源·html
JCJC错别字检测-田春峰13 小时前
字根秀秀 HTML 托管现已支持“用户登录”功能,一键变身 Web App!
前端·html·web app·网页托管
z落落13 小时前
C# 数组高阶函数(Find/FindAll/Exists/ForEach/All/Any)
javascript·数据结构·算法
胡楚昊13 小时前
在好靶场的WEB海洋遨游
前端
天机️灵韵13 小时前
Tauri 2.0与Electron的桌面应用技术选型比较
前端·electron·前端框架
之歆13 小时前
Day20_PC 端电商商品详情页前端实战:从布局到放大镜与选项卡
开发语言·前端·javascript·css·less
问心无愧051313 小时前
ctf show web入门259
android·前端·笔记