You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Electron同一窗口中同时加载自有应用和YouTube并分左右块展示

实现效果参考

实现方案

你之前使用BrowserView无法实现的核心原因是没有配合主窗口的自定义布局,BrowserView本身不自带标题栏渲染能力,需要和主窗口的DOM结构配合使用就能实现你要的左右分栏+双自定义标题栏效果,具体实现步骤如下:

1. 主窗口基础配置

首先创建无边框主窗口,关闭系统自带标题栏,交给你自己实现自定义标题栏:

const { app, BrowserWindow, BrowserView, ipcMain, path } = require('electron')
let mainWindow

app.whenReady().then(() => {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    frame: false, // 关闭系统标题栏是实现自定义双标题栏的核心前提
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      preload: path.join(__dirname, 'preload.js') // 安全要求高的场景建议用预加载脚本替代直接开启nodeIntegration
    }
  })
  mainWindow.loadFile('layout.html') // 主窗口加载本地布局文件
})

2. 编写主窗口布局文件

本地创建layout.html,实现顶部左右双标题栏、中间可拖拽分隔条的基础布局:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    /* 顶部标题栏总容器,支持拖拽移动窗口 */
    .title-bar {
      height: 40px;
      display: flex;
      width: 100%;
      -webkit-app-region: drag;
    }
    /* 左侧YouTube标题栏 */
    .yt-title {
      width: 50%;
      background: #212121;
      color: #fff;
      line-height: 40px;
      padding: 0 16px;
    }
    /* 右侧自有应用标题栏 */
    .app-title {
      flex: 1;
      background: #ffffff;
      color: #333;
      line-height: 40px;
      padding: 0 16px;
      border-left: 1px solid #eee;
    }
    /* 标题栏操作按钮,取消拖拽属性保证可点击 */
    .action-btn {
      -webkit-app-region: no-drag;
      float: right;
      margin-left: 8px;
      width: 24px;
      height: 24px;
      line-height: 24px;
      text-align: center;
      margin-top: 8px;
      border: none;
      background: transparent;
      color: inherit;
      cursor: pointer;
      border-radius: 4px;
    }
    .action-btn:hover {
      background: rgba(255,255,255,0.1);
    }
    .app-title .action-btn:hover {
      background: rgba(0,0,0,0.1);
    }
    /* 可拖拽分隔条,用来调整左右栏宽度 */
    .resizer {
      width: 4px;
      background: #eee;
      cursor: col-resize;
      -webkit-app-region: no-drag;
      position: absolute;
      left: 50%;
      top: 0;
      bottom: 0;
      z-index: 10;
    }
  </style>
</head>
<body>
  <div class="title-bar">
    <div class="yt-title">
      YouTube
      <button class="action-btn" onclick="window.close()">×</button>
      <button class="action-btn" onclick="mainWindow.isMaximized() ? mainWindow.unmaximize() : mainWindow.maximize()">□</button>
      <button class="action-btn" onclick="mainWindow.minimize()">_</button>
      <button class="action-btn" onclick="ipcRenderer.send('yt-reload')">↻</button>
    </div>
    <div class="app-title">
      自有应用
      <button class="action-btn" onclick="ipcRenderer.send('app-back')">←</button>
    </div>
  </div>
  <div class="resizer" id="resizer"></div>

  <script>
    const { ipcRenderer, remote } = require('electron')
    const mainWindow = remote.getCurrentWindow()
    const resizer = document.getElementById('resizer')
    const ytTitle = document.querySelector('.yt-title')
    let isResizing = false

    // 分隔条拖拽逻辑
    resizer.addEventListener('mousedown', (e) => {
      isResizing = true
      document.addEventListener('mousemove', handleResize)
      document.addEventListener('mouseup', () => {
        isResizing = false
        document.removeEventListener('mousemove', handleResize)
        ipcRenderer.send('adjust-view-size', ytTitle.offsetWidth)
      })
    })

    function handleResize(e) {
      if (!isResizing) return
      const newLeftWidth = e.clientX
      // 限制左右栏最小宽度为300px,避免布局错乱
      if (newLeftWidth < 300 || newLeftWidth > window.innerWidth - 300) return
      ytTitle.style.width = `${newLeftWidth}px`
      resizer.style.left = `${newLeftWidth}px`
    }
  </script>
</body>
</html>

3. 创建双BrowserView并绑定布局

主进程中主窗口加载完成后,创建两个BrowserView分别加载YouTube和自有应用,并且监听窗口大小变化、分隔条拖拽事件动态调整两个View的尺寸:

// 接之前主窗口初始化的代码
mainWindow.webContents.on('did-finish-load', () => {
  // 创建YouTube对应的BrowserView
  const ytView = new BrowserView({
    webPreferences: {
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: true // 第三方页面建议开启沙箱保证安全
    }
  })
  ytView.webContents.loadURL('https://www.youtube.com')
  mainWindow.addBrowserView(ytView)

  // 创建自有应用对应的BrowserView
  const appView = new BrowserView({
    webPreferences: {
      // 根据自有应用的需求配置对应权限
      nodeIntegration: true,
      contextIsolation: false
    }
  })
  appView.webContents.loadURL('http://localhost:3000') // 替换为你自有应用的地址
  mainWindow.addBrowserView(appView)

  // 统一的视图尺寸调整方法
  const adjustViewSize = (leftWidth = 600) => {
    const [winWidth, winHeight] = mainWindow.getSize()
    ytView.setBounds({
      x: 0,
      y: 40, // 避开顶部40px的自定义标题栏区域
      width: leftWidth,
      height: winHeight - 40
    })
    appView.setBounds({
      x: leftWidth,
      y: 40,
      width: winWidth - leftWidth,
      height: winHeight - 40
    })
  }

  // 初始化视图尺寸
  adjustViewSize()
  // 监听主窗口大小变化,自动调整视图尺寸
  mainWindow.on('resize', () => adjustViewSize())
  // 监听分隔条拖拽事件,调整视图尺寸
  ipcMain.on('adjust-view-size', (e, leftWidth) => adjustViewSize(leftWidth))
  // 监听标题栏操作按钮事件
  ipcMain.on('yt-reload', () => ytView.webContents.reload())
  ipcMain.on('app-back', () => appView.webContents.goBack())
})
注意事项
  • 如果对安全要求较高,不要直接在主窗口开启nodeIntegration: true,改用预加载脚本暴露需要的IPC能力即可
  • BrowserView的层级比主窗口DOM层级高,所以标题栏和分隔条的z-index不需要太高,本身就在BrowserView之上不会被遮挡
  • 如果需要实现两个区域的独立滚动、独立上下文,BrowserView是官方推荐的最优方案,比webview标签性能和隔离性更好

内容的提问来源于stack exchange,提问作者ptimer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 21:36:02