javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > Electron Vite调用网页记录

使用Electron+Vite实现调用网页记录功能

作者:栀椩

想要在桌面应用里流畅嵌套网页,这篇实战教程教你用 Electron + Vue 轻松实现,通过iframe加载外部网页,无需繁杂配置,一步到位,手把手拆解核心代码,涵盖权限控制与跨域通信,需要的朋友可以参考下

近期要开发桌面应用程序,实现调用网页功能,记录一下

组件内使用 <iframe>加载外部网页。使用 JavaScript。

一、为什么选择<iframe>?

如果需求是 独立的 session 隔离preload 注入细粒度网络事件拦截,请考虑 WebContentsView(但代码复杂度较高)。对于普通的内嵌展示,<iframe>足够。

二、项目初始化

使用electron-vite脚手架(推荐)

npm create @quick-start/electron-vite my-app 
cd my-app
npm install

然后按下面的结构创建文件和目录(vue创建项目时,会自动生成目录结构)。

三、项目结构(以 Vue 为例)

my-app/
├── src/
│   ├── main/
│   │   └── index.js          # 主进程入口
│   ├── preload/
│   │   └── index.js          # 预加载脚本
│   └── renderer/
│       ├── index.html        # 渲染进程入口 HTML
│       └── src/
│           ├── main.js       # Vue 应用入口
│           ├── App.vue       # 根组件
│           └── components/
│               └── IframeView.vue   # iframe 组件
├── electron.vite.config.mjs  # 配置文件(ESM)
├── package.json
└── ...

四、核心配置与代码

1.electron.vite.config.mjs(会自动生成,无需手动修改)

import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'   

export default defineConfig({
  main: {
    plugins: [externalizeDepsPlugin()],
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, 'src/main/index.js')
        }
      }
    }
  },
  preload: {
    plugins: [externalizeDepsPlugin()],
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, 'src/preload/index.js')
        }
      }
    }
  },
  renderer: {
    root: resolve(__dirname, 'src/renderer'),
    plugins: [vue()],          
    build: {
      rollupOptions: {
        input: {
          index: resolve(__dirname, 'src/renderer/index.html')
        }
      }
    }
  }
})

2.package.json关键字段(会自动生成,无需手动修改)

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "./out/main/index.js",
  "scripts": {
    "dev": "electron-vite dev",
    "build": "electron-vite build",
    "preview": "electron-vite preview"
  },
  "dependencies": {
    "electron": "^30.0.0",
    "vite": "^5.4.0",
    "electron-vite": "^2.3.0",
    "@vitejs/plugin-vue": "^5.0.0",
    "vue": "^3.4.0"
  },
  "devDependencies": {
    "electron-builder": "^24.13.0"
  }
}

3. 主进程 —src/main/index.js

主进程创建窗口,不需要设置 webviewTag: true,iframe 是标准 HTML,默认可用。

import { app, BrowserWindow, shell } from 'electron'
import { join } from 'path'
import { is } from '@electron-toolkit/utils'

function createWindow() {
  const mainWindow = new BrowserWindow({
    width: 1100,
    height: 750,
    show: false,
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false,          // 关闭沙箱以便 iframe 可正常加载
      contextIsolation: true,
      nodeIntegration: false
    }
  })

  // 开发模式:加载 Vite 开发服务器
  if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
    mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
  } else {
    // 生产模式:加载打包后的文件
    mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
  }

  mainWindow.once('ready-to-show', () => {
    mainWindow.show()
  })

  // 外部链接用默认浏览器打开(防止新窗口弹出)
  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
    shell.openExternal(url)
    return { action: 'deny' }
  })
}

app.whenReady().then(() => {
  createWindow()
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

4. 预加载脚本 —src/preload/index.js(可选)

如果需要向渲染进程暴露一些 API(例如通知、文件操作),可以写在这里。本例中仅作示范,实际可按需增减。

import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electronAPI', {
  platform: process.platform,
  sendMessage: (channel, data) => ipcRenderer.send(channel, data),
  onMessage: (channel, callback) => {
    ipcRenderer.on(channel, (_event, ...args) => callback(...args))
  }
})

5. 渲染进程 — Vue 组件方式(推荐)

src/renderer/index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Electron + Iframe</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="./src/main.js"></script>
</body>
</html>

src/renderer/src/main.js

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

src/renderer/src/App.vue

<template>
  <div id="app-root">
    <h2>Electron 内嵌网页 (iframe)</h2>
    <IframeView />
  </div>
</template>

<script setup>
import IframeView from './components/IframeView.vue'
</script>

src/renderer/src/components/IframeView.vue

这是一个带地址栏、前进后退的 iframe 组件。

<template>
  <div class="iframe-wrapper">
    <!-- 工具栏 -->
    <div class="toolbar">
      <input
        v-model="url"
        type="text"
        placeholder="输入网址,按回车访问"
        @keyup.enter="navigate"
      />
      <button @click="goBack" :disabled="!canGoBack">←</button>
      <button @click="goForward" :disabled="!canGoForward">→</button>
      <button @click="reload">⟳</button>
    </div>

    <!-- iframe 区域 -->
    <iframe
      ref="iframeRef"
      :src="currentUrl"
      sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
      frameborder="0"
      style="width:100%; height:600px;"
      @load="onIframeLoad"
    ></iframe>

    <!-- 状态提示 -->
    <div class="status">{{ statusText }}</div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const url = ref('https://www.baidu.com')
const currentUrl = ref('https://www.baidu.com')
const iframeRef = ref(null)
const canGoBack = ref(false)
const canGoForward = ref(false)
const statusText = ref('')

// 由于 iframe 没有内置的 history API,我们手动维护历史记录
const historyStack = ref(['https://www.baidu.com'])
const historyIndex = ref(0)

function navigate() {
  let targetUrl = url.value.trim()
  if (!targetUrl) return
  if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
    targetUrl = 'https://' + targetUrl
  }
  // 更新历史
  historyStack.value = historyStack.value.slice(0, historyIndex.value + 1)
  historyStack.value.push(targetUrl)
  historyIndex.value = historyStack.value.length - 1
  currentUrl.value = targetUrl
  updateNavButtons()
}

function goBack() {
  if (historyIndex.value > 0) {
    historyIndex.value--
    currentUrl.value = historyStack.value[historyIndex.value]
    url.value = currentUrl.value
    updateNavButtons()
  }
}

function goForward() {
  if (historyIndex.value < historyStack.value.length - 1) {
    historyIndex.value++
    currentUrl.value = historyStack.value[historyIndex.value]
    url.value = currentUrl.value
    updateNavButtons()
  }
}

function reload() {
  // 强制刷新 iframe:重新设置 src
  const tempSrc = currentUrl.value
  currentUrl.value = ''
  setTimeout(() => {
    currentUrl.value = tempSrc
  }, 50)
}

function updateNavButtons() {
  canGoBack.value = historyIndex.value > 0
  canGoForward.value = historyIndex.value < historyStack.value.length - 1
}

function onIframeLoad() {
  statusText.value = '页面加载完成'
}
</script>

<style scoped>
.iframe-wrapper {
  display: flex;
  flex-direction: column;
  height: 100%;
  font-family: sans-serif;
}
.toolbar {
  display: flex;
  gap: 6px;
  padding: 8px;
  background: #f5f5f5;
  border-bottom: 1px solid #ddd;
}
.toolbar input {
  flex: 1;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
}
.toolbar button {
  padding: 6px 14px;
  cursor: pointer;
  border: 1px solid #aaa;
  background: white;
  border-radius: 4px;
}
.toolbar button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.status {
  padding: 4px 8px;
  color: #666;
  font-size: 12px;
}
</style>

五、开发与生产环境

六、打包发布

npm run build:win

也可以打包成Linux下的安装包

七、重要注意事项

1.sandbox属性

2. 跨域与通信

3. 外部链接在新窗口打开

4. 关于webSecurity

5. 性能

以上就是使用Electron+Vite实现调用网页记录功能的详细内容,更多关于Electron Vite调用网页记录的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文