窗口

主窗口隐藏和恢复

主窗口

为什么需要 主窗口?

一个应用存在着许多的窗口,需要一个窗口作为 __主窗口__,如果该窗口关闭,则意味着整个应用被关闭。

场景:在应用只有一个页面的时,用户点击关闭按钮,不想让整个应用关闭,而是隐藏;
例如:其他的app,像微信,QQ等桌面端。

主进程代码示例,完整代码在本应用的app/browser-window/index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let mainWindowId: number

const browserWindow = new BrowserWindow()

// 记录下主窗口id
if (!mainWindowId) {
mainWindowId = browserWindow.id
}

browserWindow.on('close', event => {
// 如果关闭的是主窗口,阻止
if (browserWindow.id === mainWindowId) {
event.preventDefault()
browserWindow.hide()
}
})

恢复主窗口显示

1
2
3
4
5
6
const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
mainWindow.restore()
// windows下如果hide之后不调用show方法而是只调用restore方法就会导致页面挂住不能用
mainWindow.show()
}

强制关闭主窗口

1
2
3
4
5
const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
mainWindowId = -1
mainWindow.close()
}

存在的问题

因为阻止了close事件,导致 关机 时无法关闭 __主窗口__,可以使用如下代码:

1
2
3
app.on('before-quit', () => {
closeMainWindow()
})

注:macOS Linux Windows 完整文档

为避免启动 __多个应用__;

1
2
3
4
5
6
7
app.on('second-instance', () => {
const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
mainWindow.restore()
mainWindow.show()
}
})

注:macOS Linux Windows 完整文档

首次启动应用程序、尝试在应用程序已运行时或单击 应用程序坞站任务栏图标 时重新激活它;

1
2
3
4
5
6
app.on('activate', () => {
if (mainWindow) {
mainWindow.restore()
mainWindow.show()
}
})

注:macOS 文档

双击托盘图标 打开app,完整代码见app/tray

1
2
3
4
5
6
tray.on('double-click', () => {
if (mainWindow) {
mainWindow.restore()
mainWindow.show()
}
})

注:macOS Windows 文档

实例

你可以操作本应用的主窗口

文档

如果你还是新手,或者没有开发过electron应用,浏览器打开 打造你的第一个 Electron 应用

全屏、最大化、最小化、关闭

全屏

创建时进入全屏

配置new BrowserWindow({ fullscreen:true })

1
2
3
4
5
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'100px' }
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ fullscreen:true,fullscreenable:true })
win.loadURL('https://github.com')

使用API进入全屏

确保当前窗口的fullscreenable:true,以下API才能使用

  1. win.setFullScreen(flag),设置全屏状态;
  2. win.setSimpleFullScreen(flag)macOS下独有,设置简单全屏。

全屏状态的获取

  1. win.fullScreen,来判断当前窗口是否全屏;
  2. win.isFullScreen()macOS独有;
  3. win.isSimpleFullScreen()macOS独有。

全屏事件的监听

  1. rezise 调整窗口大小后触发;
  2. enter-full-screen 窗口进入全屏状态时触发;
  3. leave-full-screen 窗口离开全屏状态时触发;
  4. enter-html-full-screen 窗口进入由HTML API 触发的全屏状态时触发;
  5. leave-html-full-screen 窗口离开由HTML API触发的全屏状态时触发。

HTML API无法和窗口联动

试一试

1
2
3
4
5
6
7
8
9
10
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'200px' }
const path = require('path')
const { BrowserWindow } = require('electron')
const BaseWebPreferences = {
nodeIntegration: true,
preload: path.resolve(__dirname, './fullScreen.js'),
};
const win = new BrowserWindow({ webPreferences: BaseWebPreferences })
win.loadURL('file:///' + path.resolve(__dirname, '../playground/index.html#/demo/full-screen'))

使用按钮全屏和退出全屏是可以的,但是先点击左上角🚥全屏,再使用按钮退出全屏,是不行的。因为无法知道当前的状态是全屏,还是不是全屏。

解决办法:,将win.setFullScreen(flag)方法挂载到窗口的window

最大化、最小化

创建窗口配置

完整API文档

1
2
3
4
5
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'100px' }
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ minWidth:300,minHeight:300,maxWidth:500,maxHeight:500,width:600,height:600 })
win.loadURL('https://github.com')

当使用 minWidth/maxWidth/minHeight/maxHeight 设置最小或最大窗口大小时, 它只限制用户。 它不会阻止您将不符合大小限制的值传递给 setBounds/setSizeBrowserWindow 的构造函数。

相关事件

事件名称 触发条件
maximize 窗口最大化时触发
unmaximize 当窗口从最大化状态退出时触发
minimize 窗口最小化时触发
restore 当窗口从最小化状态恢复时触发

相关状态API

  1. win.minimizable 窗口是否可以最小化
  2. win.maximizable 窗口是否可以最大化
  3. win.isMaximized() 是否最大化
  4. win.isMinimized() 是否最小化

控制API

  1. win.maximize() 使窗口最大化
  2. win.unmaximize() 退出最大化
  3. win.minimize() 使窗口最小化
  4. win.unminimize() 退出最小化

窗口恢复

win.restore() 将窗口从最小化状态恢复到以前的状态。

创建和管理窗口

通过electronBrowserWindow模块,我们可以轻松 创建管理 窗口。

在浏览器打开 完整的API文档

每个应用,因为业务的不同;创建和管理窗口的方式和流程也大不相同;这里我们以下流程为例:

window create

创建窗口

通过BrowserWindow,来 创建 或者 管理 新的浏览器窗口,每个浏览器窗口都有一个进程来管理。

这里,我们把 创建管理__,分为 __用户行为 和 __应用本身__。

应用本身 即是:应用启动 ——> 窗口创建 –> 窗口展示
用户行为 即是:窗口已经创建完毕,用户点击a链接而创建的窗口的行为

简单创建一个窗口

1
2
3
4
5
// @@code-renderer: runner
// @@code-props: { hideRight: false, height:'100px' }
const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com');

优化

__问题__:electronBrowserWindow模块在创建时,如果没有配置show:false,在创建之时就会显示出来,且默认的背景是白色;然后窗口请求HTML,会出现闪烁。

完整api文档

解决

1
2
3
4
5
6
7
8
9
10
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'200px' }
const { BrowserWindow } = require('electron');
const win = new BrowserWindow({ show:false,x:100,y:100 });

win.loadURL('https://github.com');

win.on('ready-to-show',()=>{
win.show();
})

__对比上面简单创建窗口__,可以明显看出2者的区别

new BrowserWindow(options)options配置文档

管理窗口

管理应用创建的窗口

BrowserWindow模块在创建窗口时,会返回 窗口实例

在这里使用Map对象来存储这些 窗口实例

1
2
3
4
5
6
7
8
9
10
const BrowserWindowsMap = new Map<number, BrowserWindow>()
let mainWindowId: number;

const browserWindows = new BrowserWindow({ show:false })
browserWindows.loadURL('https://github.com')
browserWindows.once('ready-to-show', () => {
browserWindows.show()
})
BrowserWindowsMap.set(browserWindow.id, browserWindow)
mainWindowId = browserWindow.id // 记录当前窗口为主窗口

代码中提到 __主窗口__,什么是主窗口?主窗口隐藏和恢复章节

__窗口被关闭__,得把Map中的实例删除。

1
2
3
browserWindow.on('closed', () => {
BrowserWindowsMap?.delete(browserWindowID)
})

管理用户创建的窗口

一个窗口中存在许多的链接,不管是链接跳转,或是创建新的窗口,我们都需要管理这些窗口。

使用new-window监听窗口创建
使用new-window可监听 新窗口 的创建。浏览器打开完整文档

核心代码如下:

1
2
3
4
5
6
7
8
// 创建窗口监听
browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
/** @params {string} disposition
* new-window : window.open调用
* background-tab: command+click
* foreground-tab: 右键点击新标签打开或点击a标签target _blank打开
* /
})

注:关于disposition字段的解释,移步electron文档electron源码chrome 源码

扩展new-window

  • 可以被 当前窗口的 new-window事件捕捉到的
1
window.open('https://github.com')
1
<a href='https://github.com' target='__blank'>链接</a>
  • 不可被 当前窗口的 new-window事件捕捉到的
    以下api需要窗口 集成node,即 主进程 创建时需配置
    1
    2
    const { BrowserWindow } = require('electron')
    const parent = new BrowserWindow({ webPreferences:{nodeIntegration: true}});
    渲染进程 使用BrowserWindow创建窗口
    1
    2
    3
    4
    const { BrowserWindow } = require('electron').remote

    const win = new BrowserWindow()
    win.loadURL('https://github.com')
    应用new-window
    通过 __默认浏览器 来打开 __第三方链接
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import { shell } from 'electron'
    function openExternal(url: string) {
    const HTTP_REGEXP = /^https?:\/\//
    // 非http协议不打开,防止出现自定义协议等导致的安全问题
    if (!HTTP_REGEXP) {
    return false
    }
    try {
    await shell.openExternal(url, options)
    return true
    } catch (error) {
    console.error('open external error: ', error)
    return false
    }
    }
    // 创建窗口监听
    browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
    if (disposition === 'foreground-tab') {
    // 阻止鼠标点击链接
    event.preventDefault()
    openExternal(url)
    }
    })

关闭窗口

关闭窗口或者隐藏的API

win.close()

  1. 关闭页面,如果阻止close事件,将不会关闭页面,这会 __阻止计算机关闭__;
  2. 关闭页面的服务,如websocket,下次打开窗口,窗口中的页面会 __重新渲染__;
  3. 通过这个API触发的close事件在 unloadbeforeunload之前触发,通过这点可以实现 __关闭时触发弹窗__;
  4. 会被closed事件捕捉到。

__例子__:实现关闭窗口之前触发弹窗
代码如下:

1
2
3
4
5
6
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'100px' }
const { BrowserWindow } = require('electron');
const path = require('path');
const browserWindows = new BrowserWindow({webPreferences:{nodeIntegration: true,webSecurity: false}})
browserWindows.loadURL('file:///' + path.resolve(__dirname, '../playground/index.html#/demo/window-close'))

注:

  • 上面代码,只能应用于electron端,在web端刷新不起作用;web端无法在页面关闭或者刷新之前阻塞浏览器;close-window-model close-window-model
  • 阻止关闭窗口出现的弹窗,img元素外链url不起作用,会发出请求,但是不会渲染出来,可以使用div+background的方式;
  • 关于beforeunload完整API文档

win.destroy()

  1. 强制退出,无视close事件;
  2. 关闭页面,以及页面内的服务,下次打开窗口,窗口中的页面会重新渲染;
  3. 会被closed事件捕捉到。

win.hide()

这个隐藏窗口。

  1. 隐藏窗口,会触发hideblur事件,同样也是可以通过event.preventDefault()来阻止
  2. 只是隐藏窗口,通过win.show(),可以将窗口显现,并且会保持原来的窗口,里面的服务也不会挂断

其他

  • 关于页面可见性,可参见文档
  • 主窗口的隐藏和唤醒 的具体细节,可参见本章的 主窗口隐藏和恢复章节;
  • 窗口在被创建之后,窗口实例各种 事件触发顺序 可参见 窗口触发顺序章节;
  • __窗口通信__,可参见 窗口通信章节;
  • __无边框窗口__、__父子窗口__、__模态窗口__,参见窗口类型章节;

窗口之间的通信

主进程干预方式

主进程是可以干预渲染进程生成新的窗口的,只需要在创建窗口时,webContents 监听 new-window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import path from 'path'
import { PRELOAD_FILE } from 'app/config'
import { browserWindow } from 'electron';

const BaseWebPreferences: Electron.BrowserWindowConstructorOptions['webPreferences'] = {
nodeIntegration: true,
webSecurity: false,
preload: path.resolve(__dirname, PRELOAD_FILE),
enableRemoteModule:true,
}


// 创建窗口监听
browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
event.preventDefault()
// 在通过BrowserWindow创建窗口
const win = new BrowserWindow({
show:false,
webPreferences: {
...BaseWebPreferences,
additionalArguments:[`--parentWindow=${browserWindow.id}`] // 把父窗口的id传过去
enableRemoteModule:true
}
});
win.loadURl(url);
win.once('ready-to-show',()=>{
win.show()
})
})

在preload.js文件window.process.argv,便能拿到父窗口的id,window.process.argv是一个字符串数组,可以使用yargs来解析

preload.js 代码

1
2
import { argv } from 'yargs'
console.log(argv);
拿到父窗口的id,便可以通信了

试一试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'600px' }
const path = require('path')
const { BrowserWindow } = require('electron')

const BaseWebPreferences = {
// // 集成node
nodeIntegration: true,
// // 禁用同源策略
// webSecurity: false,
// 预加载脚本 通过绝对地址注入
preload: path.resolve(__dirname, './communication1.js'),
enableRemoteModule:true
}

// 主窗口代码
const parent = new BrowserWindow({ webPreferences: BaseWebPreferences, x: 100, y: 0 })
parent.loadURL(
'file:///' + path.resolve(__dirname, '../playground/index.html#/demo/communication-part1/main')
)

parent.webContents.on('new-window', (event, url, frameName, disposition) => {
event.preventDefault()

// 在通过BrowserWindow创建窗口 // 子窗口代码
const son = new BrowserWindow({
webPreferences: {
...BaseWebPreferences,
additionalArguments: ['--parentWindowId=' + parent.id],
},
})
son.webContents.openDevTools()
son.loadURL(
'file:///' +
path.resolve(__dirname, '../playground/index.html#/demo/communication-part1/client'),
)
})

其余代码如下:
主窗口代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { ReactElement, useEffect } from 'react'
import style from '../style.module.less'

export default function Communication(): ReactElement {
useEffect(() => {
document.title = '父窗口'
}, [])

return (
<div className={style.wrap}>
<a href='http://www.github.com' target='__blank'>
通过a标签target=__blank打开新的窗口
</a>
<div
onClick={() => {
window.open('http://www.github.com')
}}>
通过window.open打开新的窗口
</div>
</div>
)
}

子窗口代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { ReactElement, useEffect, useState } from 'react'
import style from '../style.module.less'

const COUNT_NUM = 5

export default function Communication(): ReactElement {
const [num, setNum] = useState(COUNT_NUM)

useEffect(() => {
document.title = '子窗口'
let timer: NodeJS.Timeout

if (num > 0) {
timer = setTimeout(() => {
setNum(num - 1)
}, 1000)
} else {
// @ts-ignore
window.send('hello')
window.close()
}
return () => {
timer && clearTimeout(timer)
}
}, [num])

return <div className={style.countDown}>子窗口 {num} 秒之后,请看主窗口</div>
}

父子窗口通信

和主进程干预,通过ipc通信方式差不多,只是利用父子窗口这点,不用通过additionalArguments传递父窗口id,在子窗口通过window.parent,就可以拿到父窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
event.preventDefault()

// 在通过BrowserWindow创建窗口
const win = new BrowserWindow({
show:false,
webPreferences:BaseWebPreferences,
parent:browserWindow // 添加父窗口
});
win.loadURl(url);
win.once('ready-to-show',()=>{
win.show()
})

})

弊端:子窗口永远在父窗口之上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'600px' }
const path = require('path')
const { BrowserWindow } = require('electron')

const BaseWebPreferences = {
// // 集成node
nodeIntegration: true,
// // 禁用同源策略
// webSecurity: false,
// 预加载脚本 通过绝对地址注入
preload: path.resolve(__dirname, './communication2.js'),
enableRemoteModule:true
}

// 主窗口代码
const parent = new BrowserWindow({ webPreferences: BaseWebPreferences, left: 100, top: 0 })
parent.loadURL(
'file:///' + path.resolve(__dirname, '../playground/index.html#/demo/communication-part2/main'),
)
parent.webContents.on('new-window', (event, url, frameName, disposition) => {
// 阻止默认事件
event.preventDefault()
// 在通过BrowserWindow创建窗口
// 子窗口代码
const son = new BrowserWindow({
webPreferences: BaseWebPreferences,
parent,
width: 400,
height: 400,
alwaysOnTop: false,
})
// son.webContents.openDevTools();
son.loadURL(
'file:///' +
path.resolve(__dirname, '../playground/index.html#/demo/communication-part2/client'),
)
})

其余代码如下:

主窗口代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { ReactElement, useEffect } from 'react'
import style from '../style.module.less'

export default function Communication(): ReactElement {

useEffect(() => {
document.title = '父窗口'
}, [])

return (
<div className={style.wrap}>
<a href='http://www.github.com' target='__blank'>
通过a标签target=__blank打开新的窗口
</a>
<div
onClick={() => {
window.open('http://www.github.com')
}}>
通过window.open打开新的窗口
</div>
</div>
)
}

子窗口代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { ReactElement, useEffect, useState } from 'react'
import style from '../style.module.less'

const COUNT_NUM = 5

export default function Communication(): ReactElement {
const [num, setNum] = useState(COUNT_NUM)

useEffect(() => {
document.title = '子窗口'
let timer: NodeJS.Timeout

if (num > 0) {
timer = setTimeout(() => {
setNum(num - 1)
}, 1000)
} else {
// @ts-ignore
window.sendToParent('hello')
window.close()
}
return () => {
timer && clearTimeout(timer)
}
}, [num])

return <div className={style.countDown}>子窗口 {num} 秒之后,请看主窗口</div>
}

使用window.open

窗口事件

窗口加载时

从上到下,依次执行

环境 事件 触发时机
webPreferences的preload - 在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成Node, 此脚本都可以访问所有Node API 脚本路径为文件的绝对路径。
webContents did-start-loading 当tab中的旋转指针(spinner)开始旋转时,就会触发该事件
webContents did-start-navigation 当窗口开始导航是,触发该事件
窗口中的JavaScript DOMContentLoaded 初始的 HTML 文档被完全加载和解析完成
窗口中的JavaScript load 页面资源全部加载完成之时
BrowserWindow实例 show 窗口显示时触发时
webContents did-frame-navigate frame导航结束时时
webContents did-navigate main frame导航结束时时
BrowserWindow实例 page-title-updated 文档更改标题时触发
webContents page-title-updated 文档更改标题时触发
webContents dom-ready 一个框架中的文本加载完成后触发该事件
webContents did-frame-finish-load 当框架完成导航(navigation)时触发
webContents did-finish-load 导航完成时触发,即选项卡的旋转器将停止旋转
webContents did-stop-loading 当tab中的旋转指针(spinner)结束旋转时,就会触发该事件

##. 窗口加载完毕,用户触发事件(不包括resize和move)

事件 作用
page-title-updated 文档更改标题时触发
blur 当窗口失去焦点时触发
focus 当窗口获得焦点时触发
hide 窗口隐藏
show 窗口显示
maximize 窗口最大化时触发(mac是双击title)
unmaximize 当窗口从最大化状态退出时触发
enter-full-screen 窗口进入全屏状态时触发
leave-full-screen 窗口离开全屏状态时触发
enter-html-full-screen 窗口进入由HTML API 触发的全屏状态时触发
leave-html-full-screen 窗口离开由HTML API触发的全屏状态时触发
always-on-top-changed 设置或取消设置窗口总是在其他窗口的顶部显示时触发。
app-command window linux独有

用户移动窗口

  1. 移动窗口之前 will-move
  2. 移动窗口中 move
  3. 移动之后 moved

用户改变窗口大小

  1. 改变之前 will-resize
  2. 改变之后 resize

窗口的内容异常事件(webContent事件)

事件名 错误类型
unresponsive 网页变得未响应时触发
responsive 未响应的页面变成响应时触发
did-fail-load 加载失败,错误码
did-fail-provisional-load 页面加载过程中,执行了window.stop()
did-frame-finish-load
crashed 渲染进程崩溃或被结束时触发
render-process-gone 渲染进程意外失败时发出
plugin-crashed 有插件进程崩溃时触发
certificate-error 证书的链接验证失败
preload-error preload.js抛出错误

窗口关闭(包括意外关闭)

按触发顺序依次:(带window的是渲染进程中的)

  • 关闭之前:close
  • window.onbeforeunload
  • window.onunload
  • 关闭之后:closed

文档

  1. BrowserWindow;
  2. webContents;
  3. DOMContended

窗口的聚焦和失焦

聚焦

创建窗口时配置:

1
2
3
4
5
6
7
8
9
10
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'150px' }
const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com')

setTimeout(()=>{
const win2 = new BrowserWindow({x:100,y:100})
win2.loadURL('https://www.baidu.com')
},3000)

focusable:true 窗口便可聚焦,便可以使用聚焦的api

focusable:falseWindows 中设置 focusable: false 也意味着设置了skipTaskbar: true. 在 Linux 中设置 focusable: false 时窗口停止与 wm 交互, 并且窗口将始终置顶。

以下讨论的情况仅为focusable:true情况下

1
2
const { BrowserWindow } = require('electron');
const win = new BrowserWindow() // focusable:true 为默认配置

关于聚焦的api

api 功能
BrowserWindow.getFocusedWindow() 来获取聚焦的窗口
win.isFocused() 判断窗口是否聚焦
win.on('focus',cb) 来监听窗口是否聚焦
win.focus() 手动聚焦窗口

其他api副作用和聚焦有关的:

api 功能
win.show() 显示窗口,并且聚焦于窗口
win.showInactive() 显示窗口,但是不会聚焦于窗口

失焦

关于失焦的api

api 功能
win.blur() 取消窗口聚焦
win.on('blur',cb) 监听失焦

其他api副作用和失焦有关的:

api 功能
win.hide() 隐藏窗口,并且会触发失焦事件

窗口类型

无边框窗口

描述:

无边框窗口是不带外壳(包括窗口边框、工具栏等),只含有网页内容的窗口

实现

Windows macOS Linux

1
2
3
4
5
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'100px' }
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ width: 800, height: 600, frame: false })
win.loadURL('https://github.com')

macOS下,还有不同的实现方式,完整文档

macOS 下独有的无边框

  • 配置titleBarStyle: 'hidden'

    返回一个隐藏标题栏的全尺寸内容窗口,在左上角仍然有标准的窗口控制按钮(俗称“红绿灯”)

1
2
3
4
5
6
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'100px' }
// 创建一个无边框的窗口
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ titleBarStyle: 'hidden' })
win.loadURL('https://github.com')

效果如下:
window-type-frame

  • 配置titleBarStyle: 'hiddenInset'

    返回一个另一种隐藏了标题栏的窗口,其中控制按钮到窗口边框的距离更大。

1
2
3
4
5
6
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'100px' }
// 创建一个无边框的窗口
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })
win.loadURL('https://github.com')

效果如下:
window-type-frame2

1.1.0.3.3. 配置titleBarStyle: 'customButtonsOnHover'

效果如下:
window-type-frame2

无边框窗口常见的问题

  • 窗口顶部无法拖拽的问题
    默认情况下, 无边框窗口是不可拖拽的。 应用程序需要在 CSS 中指定 -webkit-app-region: drag 来告诉 Electron 哪些区域是可拖拽的(如操作系统的标准标题栏),在可拖拽区域内部使用 -webkit-app-region: no-drag 则可以将其中部分区域排除。 请注意, 当前只支持矩形形状。完整文档

使用-webkit-app-region: drag 来实现拖拽,但是会导致内部的click事件失效。这个时候可以将需要click元素设置为-webkit-app-region: no-drag。具体的细节可看issues

为了不影响窗口,这里拖拽的代码,应该在preload触发。
核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 在顶部插入一个可以移动的dom
function initTopDrag() {
const topDiv = document.createElement('div') // 创建节点
topDiv.style.position = 'fixed' // 一直在顶部
topDiv.style.top = '0'
topDiv.style.left = '0'
topDiv.style.height = '20px' // 顶部20px才可拖动
topDiv.style.width = '100%' // 宽度100%
topDiv.style.zIndex = '9999' // 悬浮于最外层
topDiv.style.pointerEvents = 'none' // 用于点击穿透
// @ts-ignore
topDiv.style['-webkit-user-select'] = 'none' // 禁止选择文字
// @ts-ignore
topDiv.style['-webkit-app-region'] = 'drag' // 拖动
document.body.appendChild(topDiv) // 添加节点
}

window.addEventListener('DOMContentLoaded', function onDOMContentLoaded() {
initTopDrag()
})

试一试

1
2
3
4
5
6
7
8
9
10
11
12
13
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'200px' }
const path = require('path')
const { BrowserWindow } = require('electron')

const BaseWebPreferences = {
nodeIntegration: true,
preload: path.resolve(__dirname, './windowType.js'),
}

// 主窗口代码
const win = new BrowserWindow({ webPreferences: BaseWebPreferences, frame: false })
win.loadURL('https://github.com')

父子窗口

子窗口始终在父窗口之上,在 窗口之间通信 章节中介绍到父子窗口之间的通信,窗口之间通信章节

1
2
3
4
5
6
7
8
// @@code-renderer: runner
// @@code-props: { hideRight: true, height:'100px' }
const { BrowserWindow } = require('electron')

let top = new BrowserWindow()
let child = new BrowserWindow({ parent: top })
child.show()
top.show()

模态窗口