Wer mehrere Web-Inhalte in einem Fenster braucht — etwa eine Tab-leiste mit echten Browser-Tabs, einen Side-Panel-Viewer oder eingebettete Web-Apps — nutzt seit Electron 28 die WebContentsView-API. Sie ersetzt das deprecated BrowserView mit einer saubereren Ergonomie.
Grundbeispiel
import { app, BaseWindow, WebContentsView } from 'electron';
app.whenReady().then(() => {
const win = new BaseWindow({ width: 1200, height: 800 });
// Haupt-View (z. B. Tab-Bar)
const tabBar = new WebContentsView();
tabBar.webContents.loadFile('tabs.html');
tabBar.setBounds({ x: 0, y: 0, width: 1200, height: 40 });
win.contentView.addChildView(tabBar);
// Aktiver Tab-Inhalt
const tabContent = new WebContentsView();
tabContent.webContents.loadURL('https://example.com');
tabContent.setBounds({ x: 0, y: 40, width: 1200, height: 760 });
win.contentView.addChildView(tabContent);
});BaseWindow ist das neue, leichtgewichtige Fenster ohne eigenen Web-Inhalt. Inhalt kommt ausschließlich über WebContentsView-Kinder.
BrowserWindow ist der Komfort-Wrapper, der genau einen WebContentsView automatisch hat — für Standard-Apps weiter die richtige Wahl.
BrowserView vs. WebContentsView
| BrowserView | WebContentsView | |
|---|---|---|
| Status | deprecated | moderner Standard |
| Verfügbar seit | Electron 1 | Electron 28 |
| API-Stil | Manuelle Bounds-Verwaltung in BrowserWindow | Über contentView mit Child-Views |
| Mehrere parallel | Ja, aber ohne klare Layout-API | Ja, mit Child-View-Hierarchie |
| Empfehlung | Migration auf WebContentsView | Nehmen für Neues |
Migration: addBrowserView → contentView.addChildView, setBounds weiter unverändert.
Klassischer Tab-Browser
const tabs = new Map();
let activeTabId = null;
function createTab(url) {
const id = crypto.randomUUID();
const view = new WebContentsView({
webPreferences: {
contextIsolation: true,
sandbox: true
}
});
view.webContents.loadURL(url);
tabs.set(id, view);
return id;
}
function activateTab(id) {
// Alten View entfernen
if (activeTabId && tabs.has(activeTabId)) {
win.contentView.removeChildView(tabs.get(activeTabId));
}
// Neuen View einsetzen
const view = tabs.get(id);
view.setBounds({ x: 0, y: 40, width: 1200, height: 760 });
win.contentView.addChildView(view);
activeTabId = id;
}
function closeTab(id) {
const view = tabs.get(id);
if (view) {
win.contentView.removeChildView(view);
view.webContents.close();
tabs.delete(id);
}
}Pattern: alle Tabs im Map halten, beim Tab-Wechsel den alten View aus der contentView entfernen, neuen einsetzen. Schließen erfordert webContents.close() — sonst leakt der Renderer-Prozess.
Resize-Handling
Bei Fenster-Größenänderung müssen alle Child-Views ihre Bounds aktualisieren — das macht WebContentsView nicht automatisch.
win.on('resize', () => {
const { width, height } = win.getBounds();
tabBar.setBounds({ x: 0, y: 0, width, height: 40 });
tabContent.setBounds({ x: 0, y: 40, width, height: height - 40 });
});Häufige Falle: Resize-Listener vergessen, dann skaliert Inhalt nicht mit. Pflicht-Pattern.
Z-Order und Kommunikation
// Reihenfolge der addChildView-Aufrufe = Z-Order (letzter = oben)
win.contentView.addChildView(background); // unten
win.contentView.addChildView(content); // mitte
win.contentView.addChildView(overlay); // oben
// Manuell umsortieren — ältere Variante:
win.contentView.removeChildView(content);
win.contentView.addChildView(content); // jetzt zuoberstJeder WebContentsView ist ein eigener Renderer-Prozess. Kommunikation zwischen Tab-Bar und Content geht nicht direkt — über IPC zum Main, der weiterleitet.
Wann BrowserWindow nehmen, wann BaseWindow?
| App-Typ | Empfehlung |
|---|---|
| Single-Window mit einer UI | BrowserWindow (einfacher) |
| Tab-Browser, Multi-View | BaseWindow + mehrere WebContentsView |
| App mit Side-Panel als Webview | BrowserWindow + 1-2 zusätzliche WebContentsView |
| Komplexes Custom-Layout | BaseWindow |
BrowserWindow bleibt der pragmatische Default. BaseWindow ist die explizite Wahl, wenn man volle Kontrolle über alle Views will.
Besonderheiten
BrowserView nicht mehr für Neues nehmen.
Status ist deprecated, mit klarer Empfehlung auf WebContentsView zu migrieren. In neuen Projekten direkt das moderne API verwenden — die Migration später ist sonst Aufwand für genau nichts.
Resize-Listener pflichtig.
WebContentsViews skalieren nicht automatisch mit dem Fenster. Bei jedem resize-Event manuell setBounds aufrufen. Häufige Falle, weil bei Single-View-Apps mit BrowserWindow Auto-Resize gewohnt ist.
Jeder View ist ein eigener Renderer-Prozess.
10 Tabs offen = 10 Renderer-Prozesse. Bei Memory-knappen Setups merkbar. Pattern: Tabs unsichtbar lassen statt zu schließen, oder via webContents.suspend() (Electron 31+) den Speicher reduzieren.
Z-Order über Add-Reihenfolge.
Es gibt kein dediziertes „bring-to-front", sondern nur Add/Remove. Wer dynamisch umsortiert, muss Views zwischendurch removen und wieder addChildView'en. Etwas unhandlich, aber funktioniert.
Kommunikation zwischen Views = Main als Hub.
WebContentsView A kann nicht direkt mit WebContentsView B reden. Üblicher Weg: A schickt IPC zum Main, Main forwarded an B via b.webContents.send. Bei vielen Views entsteht ein Hub-and-Spoke-Pattern im Main.
webContents.close() beim Entfernen — sonst Leak.
removeChildView versteckt den View, killt aber den Renderer-Prozess nicht. Wer einen View dauerhaft entfernen will: anschließend view.webContents.close(). Sonst sammeln sich tote Renderer im RAM.
Weiterführende Ressourcen
Externe Quellen
- WebContentsView
- BaseWindow
- BrowserView (deprecated) — Migrations-Hinweis
- WebContents