首页
/ Electron 自定义标题栏实战指南:titleBarStyle、Window Controls Overlay 与跨平台拖拽实现

Electron 自定义标题栏实战指南:titleBarStyle、Window Controls Overlay 与跨平台拖拽实现

2026-09-06 19:06:27作者:仰钰奇

在本仓库的 custom-title-bar.md 教程中,Electron 官方详细演示了如何移除操作系统默认的 window chrome,并用 HTML/CSS 构建自己的标题栏,同时让窗口在 Windows、Linux 与 macOS 上保持可拖拽、可控制的原生体验。读完本文你将掌握:titleBarStyletitleBarOverlay 的全部配置语义、app-region: drag 拖拽区域的正确用法、env(titlebar-area-*) 安全区的适配技巧,以及 macOS 交通灯按钮(traffic lights)的隐藏、位移与自定义方案。

什么是 window chrome,为什么要自定义标题栏

Electron 文档中使用的 chrome 一词并非指 Google Chrome 浏览器,而是指窗口框架中不属于网页主体内容的那部分装饰元素,包括标题栏、工具栏与窗口控制按钮。默认情况下,操作系统会为应用窗口绘制一套原生 chrome,它对简单应用足够好用,但不同平台(macOS / Windows / Linux)的观感差异很大,很难与应用自身的 UI 风格统一。

因此很多应用会选择移除系统标题栏,再用 Web 技术重绘一套跨平台观感一致的标题栏。这正是本教程围绕 BrowserWindow 的窗口配置选项展开的核心诉求。BrowserWindowBaseWindow 的子类,二者的构造选项用法可以互通,教程中的示例同样适用于 BaseWindow,详见 base-window.md

本仓库在 docs/fiddles/features/window-customization/custom-title-bar/ 下提供了与教程逐步骤对应的可运行 Fiddle 示例,共五个阶段,我们按顺序逐个实现:

步骤目录 作用
starter-code/ 最原始的默认窗口,作为动手起点
remove-title-bar/ 隐藏默认标题栏
native-window-controls/ Windows / Linux 恢复原生窗口控制按钮
custom-title-bar/ 用 HTML/CSS 绘制自定义标题栏
custom-drag-region/ 让标题栏区域可拖拽移动窗口
safe-area/ 避开原生窗口控制的安全区适配

第一步:移除默认标题栏

教程的第一步从 starter-code/main.js 开始,它只是创建了一个空配置的 BrowserWindow 并加载远程页面:

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

function createWindow () {
  const win = new BrowserWindow({})
  win.loadURL('https://example.com')
}

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

要移除默认标题栏,只需在构造器中把 titleBarStyle 设为 'hidden',这是 base-window-options.md 中定义的窗口选项之一:

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()
})

完整代码见 remove-title-bar/main.js

这里必须强调 hidden 值在不同平台上的行为差异(源码依据见 base-window-options.mdtitleBarStyle 的逐值说明):

  • macOS:标题栏被隐藏,网页内容撑满整个窗口,但左上角的原生"交通灯"关闭/最小化/最大化按钮仍然保留并可点击;
  • Windows / Linux:标题栏被隐藏后,如果不配合 titleBarOverlay: true任何窗口控制按钮都不会显示,你需要在下一节自行恢复它们;
  • 作为对照,default(默认值)在各平台呈现标准原生标题栏。

第二步:在 Windows / Linux 恢复原生窗口控制

由于 macOS 在 hidden 下仍保留交通灯按钮,需要额外处理窗口控制的只有 Windows 和 Linux。做法是在构造选项中设置 titleBarOverlay,让系统把最小化/最大化/关闭按钮重新"浮"回窗口右上角(或根据系统设置/ RTL 布局位于左侧)。

Fiddle 示例 native-window-controls/main.js 展示了推荐的平台条件写法——通过展开运算符只在非 macOS 平台注入该选项,避免影响 macOS 的交通灯行为:

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

function createWindow () {
  const win = new BrowserWindow({
    // remove the default titlebar
    titleBarStyle: 'hidden',
    // expose window controls in Windows/Linux
    ...(process.platform !== 'darwin' ? { titleBarOverlay: true } : {})
  })
  win.loadURL('https://example.com')
}

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

titleBarOverlay: true 是让窗口控制恢复显示的最简写法,此时按钮会使用系统默认配色与默认高度。如果你希望进一步自定义按钮高度与颜色,参见后文"[通过 Window Controls Overlay 定制窗口控制]"(#通过-window-controls-overlay-定制窗口控制)小节。

从构造选项文档 base-window-options.md 可以确认一个重要前提:titleBarOverlay 要求 titleBarStyle 的值不能是 default(即必须是 hiddenhiddenInsetcustomButtonsOnHover 等隐藏标题栏方案),否则该特性不生效。

第三步:用 HTML/CSS 画一个自定义标题栏

接下来不再依赖系统,改为在 webContents 里手绘标题栏。教程 custom-title-bar 阶段 的窗口逻辑与上一步一致,只是把加载内容换成同目录下的 index.html

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

function createWindow () {
  const win = new BrowserWindow({
    // remove the default titlebar
    titleBarStyle: 'hidden',
    // expose window controls in Windows/Linux
    ...(process.platform !== 'darwin' ? { titleBarOverlay: true } : {})
  })

  win.loadFile('index.html')
}

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

HTML 的结构非常简单,只需把标题栏挂载在 body 最顶部即可(完整代码见 custom-title-bar/index.html):

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
    <link href="./styles.css" rel="stylesheet">
    <title>Custom Titlebar App</title>
  </head>
  <body>
    <!-- mount your title bar at the top of you application's body tag -->
    <div class="titlebar">Cool titlebar</div>
  </body>
</html>

样式上"没有什么花哨的东西,只是 HTML 和 CSS"(见 custom-title-bar/styles.css):

body {
    margin: 0;
}

.titlebar {
  height: 30px;
  background: blue;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
}

注意 HTML 中带上了 Content-Security-Policy 元信息,它限定页面只能加载自身资源,是 Electron 官方示例的标准安全实践;内联样式需要 style-src 'self' 'unsafe-inline' 才能正常生效。

第四步:让自定义标题栏能拖动窗口

此时窗口虽然显示为自定义标题栏,但窗口无法被鼠标拖动——因为系统标题栏已被移除,没有任何区域告诉操作系统"这里可以被按住拖动"。解决办法是在自定义标题栏的 CSS 上追加 app-region: drag

body {
  margin: 0;
}
.titlebar {
  height: 30px;
  background: blue;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  app-region: drag;
}

对应 Fiddle 见 custom-drag-region/styles.css,窗口逻辑与 HTML 结构同上一步保持一致(custom-drag-region/main.jscustom-drag-region/index.html)。

这里必须补充一个极易踩坑的交互细节:drag 会吞掉其子元素的鼠标事件。如果标题栏里还有按钮、输入框、搜索框这类需要点击的交互控件(例如 macOS 风格的自定义交通灯、Windows 风格的应用菜单按钮),必须在这些子元素上显式设置 app-region: no-drag,否则它们将无法被点击。关于拖拽区域与可交互区域的完整管理方法,请继续阅读同目录下的 custom-window-interactions.md("Custom draggable regions"小节)。

第五步:用 env(titlebar-area-*) 避开原生窗口控制

还差最后一步:我们自定义的标题栏内容可能与浮在窗口上的原生窗口控制按钮(Windows/Linux 的右上角关闭/最小化按钮,或 RTL 与用户设置导致位于左侧)互相重叠。由于按钮可能出现在左侧、右侧甚至两侧,靠写死 margin 并不可靠。

Electron 提供的解法是使用一组 CSS 环境变量,让标题栏自动收缩到"不会被窗口控制遮住"的安全区:

  • env(titlebar-area-x, 0px):安全区相对于窗口的横向偏移;
  • env(titlebar-area-width, 100%):安全区可用宽度;
  • env(titlebar-area-height, 30px):安全区高度。

教程 safe-area 阶段 给出的完整写法如下(用虚线边框直观标出安全区边界):

body {
  margin: 0;
}
.titlebar {
  background: blue;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  app-region: drag;

  margin-left: env(titlebar-area-x, 0);
  width:       env(titlebar-area-width, 100%);
  height:      env(titlebar-area-height, 30px);
  box-sizing: border-box;
  border: 1px dashed red;
}

各环境变量后面的参数(如 0100%30px)是回退值:当页面未处于 Window Controls Overlay 模式、变量不可用时,标题栏会退回到"占满整行、高度 30px"的普通布局,从而保证不启用 overlay 的平台(如 macOS)也能正常渲染。配套窗口逻辑与 HTML 分别见 safe-area/main.jssafe-area/index.html

到这里,一个"可拖动、不与原生按钮重叠"的基础自定义标题栏就完成了。仓库中的测试 fixture pages/overlay.html 也使用了 titlebar-area-* 变量,可作为如何在实际页面中消费这些变量的补充参考。

进阶定制(macOS):交通灯按钮的显示与位置

macOS 用户不满足于默认的左上角原生按钮时,Electron 提供了从"显示时机"到"精确坐标"的完整控制链。

悬停才显示的 customButtonsOnHover

如果你打算在 HTML 中绘制自己的交通灯按钮,但仍希望用原生 UI 真正控制窗口,可以使用 customButtonsOnHover 风格:标题栏隐藏,原生交通灯平时不显示,只有当鼠标悬停到窗口左上角区域时才浮现出来。注意 base-window-options.md 中标注该选项目前是实验性的

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ titleBarStyle: 'customButtonsOnHover' })

调整交通灯的位置

位置控制有两个层级。若只需整体下移/内缩一段固定距离,使用 hiddenInset 风格,它会以固定量调整交通灯的纵向内边距(macOS 专用):

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })

如果需要更精细的定位,可以通过 trafficLightPosition 直接传入一组坐标(类型为 Point,即 { x, y } 像素值),通常配合 titleBarStyle: 'hidden' 使用:

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({
  titleBarStyle: 'hidden',
  trafficLightPosition: { x: 10, y: 10 }
})

程序化地显示/隐藏交通灯

也可以不依赖构造选项,改在运行时从主进程通过 win.setWindowButtonVisibility(boolean) 强制切换交通灯可见性:

const { BrowserWindow } = require('electron')

const win = new BrowserWindow()
// hides the traffic lights
win.setWindowButtonVisibility(false)

教程特别给出了一条等价性提示:由于相关 API 众多,达成同一布局有多种组合。例如 frame: false(无边框窗口)配合 win.setWindowButtonVisibility(true),最终布局效果与直接设置 titleBarStyle: 'hidden' 完全一致。你完全可以根据代码可读性选择任何一种组合。

进阶定制(跨平台):通过 Window Controls Overlay 定制窗口控制

Window Controls Overlay API 是一个 Web 标准,目标是让安装到桌面的 Web 应用能够自定义标题栏区域。Electron 通过 BrowserWindow 构造选项中的 titleBarOverlay 将其暴露给开发者:启用后,窗口控制按钮会在默认位置显露出来,且按钮下方的 DOM 区域不允许被页面内容覆盖

当你需要进一步自定义按钮外观时,可以把 titleBarOverlay 从布尔值升级为对象。教程给出了一个完整的 macOS/Windows 通用示例(base-window-options.md 中称之为 Object 形态),其字段语义如下:

字段 适用平台 说明 默认值
color Windows、Linux Window Controls Overlay 按钮所在条带的 CSS 颜色 系统颜色
symbolColor Windows、Linux 按钮图标(最小化/最大化/关闭符号)的颜色 系统颜色
height 全平台 标题栏与按钮区域高度(像素,必须是整数 系统标准高度

colorsymbolColor 支持 rgba()hsla() 以及带透明通道的 #RRGGBBAA 格式,即颜色本身可以带透明度;两个颜色属性若未指定,则回退到对应平台系统为窗口控制按钮使用的默认色:

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({
  titleBarStyle: 'hidden',
  titleBarOverlay: {
    color: '#2f3241',
    symbolColor: '#74b1be',
    height: 60
  }
})

按此配置,在 Windows/Linux 上你会得到一个深色(#2f3241)、青蓝图标(#74b1be)、高 60px 的按钮条带;在 macOS 上仅 height 生效,交通灯仍保持系统外观。

主进程启用 overlay 之后,渲染进程侧即可通过两组能力感知 overlay 区域:

  1. 只读的 JavaScript API,用于查询 overlay 当前的尺寸与颜色值;
  2. CSS 环境变量(即前文用到的 env(titlebar-area-x / titlebar-area-width / titlebar-area-height))。

因此,真正"现代化"的自定义标题栏最佳实践通常是:构造窗口时用 titleBarStyle: 'hidden' + titleBarOverlay: { color, symbolColor, height },渲染层用 app-region: drag 撑起拖拽区、用 env(titlebar-area-*) 动态收缩内容避开按钮,再配合 titleBarOverlay 的只读 API 响应布局变化(如用户切到 RTL 或修改系统设置导致按钮换边)。这与本文第五步的 safe-area Fiddle 形成了完整的闭环。

小结

从本仓库的 custom-title-bar.md 教程出发,结合 base-window-options.md 的选项权威说明与 docs/fiddles/features/window-customization/custom-title-bar/ 下的逐步骤示例,我们可以把"自定义标题栏"的实现归纳为一条可复用的跨平台路径:

  1. 移除系统 chrometitleBarStyle: 'hidden',macOS 保留交通灯,Windows/Linux 无按钮;
  2. 恢复窗口控制:Windows/Linux 追加 titleBarOverlay: true(可用对象形式自定义颜色与高度,但 titleBarStyle 不能为 default);
  3. 自绘标题栏:在 HTML 顶部放置标题栏容器;
  4. 恢复拖拽:容器加 app-region: drag,可交互子元素加 app-region: no-drag
  5. 规避遮挡:用 env(titlebar-area-x/width/height) 与合理回退值做安全区适配;
  6. macOS 精调(可选)customButtonsOnHoverhiddenInsettrafficLightPositionsetWindowButtonVisibility 按需组合。

需要继续深入时可参考仓库中的 browser-window.mdBrowserWindow 实例方法全集)、base-window-options.md(全部构造选项的平台语义)以及 custom-window-interactions.md(拖拽区、窗口置顶等更多窗口交互定制)。

登录后查看全文
热门项目推荐
相关项目推荐