作者:Kurosaki
本文主要講解Electron 窗口的 API 和一些在開發(fā)之中遇到的問(wèn)題。
官方文檔 雖然比較全面,但是要想開發(fā)一個(gè)商用級(jí)別的桌面應(yīng)用必須對(duì)整個(gè) Electron API 有較深的了解,才能應(yīng)對(duì)各種需求。
1. 創(chuàng)建窗口
通過(guò)BrowserWindow,來(lái) 創(chuàng)建 或者 管理 新的瀏覽器窗口,每個(gè)瀏覽器窗口都有一個(gè)進(jìn)程來(lái)管理。
1.1. 簡(jiǎn)單創(chuàng)建窗口
const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com');
效果如下:

1.1.2. 優(yōu)化
問(wèn)題:electron的 BrowserWindow模塊在創(chuàng)建時(shí),如果沒(méi)有配置 show:false,在創(chuàng)建之時(shí)就會(huì)顯示出來(lái),且默認(rèn)的背景是白色;然后窗口請(qǐng)求 HTML,會(huì)出現(xiàn)視覺(jué)閃爍。
解決
const { BrowserWindow } = require('electron');
const win = new BrowserWindow({ show:false });
win.loadURL('https://github.com');
win.on('ready-to-show',()=>{
win.show();
})
兩者對(duì)比有很大的區(qū)別

1.2. 管理窗口
所謂的管理窗口,相當(dāng)于主進(jìn)程可以干預(yù)窗口多少。
- 窗口的路由跳轉(zhuǎn)
- 窗口打開新的窗口
- 窗口大小、位置等
- 窗口的顯示
- 窗口類型(無(wú)邊框窗口、父子窗口)
- 窗口內(nèi)
JavaScript的node權(quán)限,預(yù)加載腳本等 - ....
這些個(gè)方法都存在于BrowserWindow模塊中。
1.2.1. 管理應(yīng)用創(chuàng)建的窗口
BrowserWindow模塊在創(chuàng)建窗口時(shí),會(huì)返回 窗口實(shí)例,這些 **窗口實(shí)例 **上有許多功能方法,我們利用這些方法,管理控制這個(gè)窗口。
在這里使用Map對(duì)象來(lái)存儲(chǔ)這些 窗口實(shí)例。
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 // 記錄當(dāng)前窗口為主窗口
窗口被關(guān)閉,得把Map中的實(shí)例刪除。
browserWindow.on('closed', () => {
BrowserWindowsMap?.delete(browserWindowID)
})
1.2.2. 管理用戶創(chuàng)建的窗口
主進(jìn)程可以控制窗口許多行為,這些行為會(huì)在后續(xù)文章一一列舉;以下以主進(jìn)程控制窗口建立新窗口的行為為例。
使用new-window監(jiān)聽(tīng)新窗口創(chuàng)建
// 創(chuàng)建窗口監(jiān)聽(tīng)
browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
/** @params {string} disposition
* new-window : window.open調(diào)用
* background-tab: command+click
* foreground-tab: 右鍵點(diǎn)擊新標(biāo)簽打開或點(diǎn)擊a標(biāo)簽target _blank打開
* /
})
注:關(guān)于
disposition字段的解釋,移步electron文檔、electron源碼、chrome 源碼
擴(kuò)展new-window
經(jīng)過(guò)實(shí)驗(yàn),并不是所有新窗口的建立,
new-window都能捕捉到的。
以下方式打開的窗口可以被new-window事件捕捉到
window.open('https://github.com')
<a target='__blank'>鏈接</a>
**
渲染進(jìn)程中使用BrowserWindow創(chuàng)建新窗口,不會(huì)被 new-window事件捕捉到
**
const { BrowserWindow } = require('electron').remote
const win = new BrowserWindow()
win.loadURL('https://github.com')
_渲染進(jìn)程訪問(wèn) __remote_ _,主進(jìn)程需配置enableRemoteModule:true _
使用這種方式同樣可以打開一個(gè)新的窗口,但是主進(jìn)程的new-window捕捉不到。
應(yīng)用new-window
new-window 控制著窗口新窗口的創(chuàng)建,我們利用這點(diǎn),可以做到很多事情;比如鏈接校驗(yàn)、瀏覽器打開鏈接等等。默認(rèn)瀏覽器打開鏈接代碼如下:
import { shell } from 'electron'
function openExternal(url: string) {
const HTTP_REGEXP = /^https?:\/\//
// 非http協(xié)議不打開,防止出現(xiàn)自定義協(xié)議等導(dǎo)致的安全問(wèn)題
if (!HTTP_REGEXP) {
return false
}
try {
await shell.openExternal(url, options)
return true
} catch (error) {
console.error('open external error: ', error)
return false
}
}
// 創(chuàng)建窗口監(jiān)聽(tīng)
browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
if (disposition === 'foreground-tab') {
// 阻止鼠標(biāo)點(diǎn)擊鏈接
event.preventDefault()
openExternal(url)
}
})
_關(guān)于 __shell_ 模塊,可以查看官網(wǎng) https://www.electronjs.org/docs/api/shell
_
1.3. 關(guān)閉窗口
**close** **事件和 ****closed** 事件
close 事件在窗口將要關(guān)閉時(shí)之前觸發(fā),但是在 DOM 的 beforeunload 和 unload 事件之前觸發(fā)。
// 窗口注冊(cè)close事件
win.on('close',(event)=>{
event.preventDefault() // 阻止窗口關(guān)閉
})
closed 事件在窗口關(guān)閉后出觸發(fā),但是此時(shí)的窗口已經(jīng)被關(guān)閉了,無(wú)法通過(guò) event.preventDefault() 來(lái)阻止窗口關(guān)閉。
win.on('closed', handler)
主進(jìn)程能夠關(guān)閉窗口的 API 有很多,但都有各自的利弊。
1.3.1. win.close()
關(guān)于這個(gè)
API的利弊
- 如果當(dāng)前窗口實(shí)例注冊(cè)并阻止
close事件,將不會(huì)關(guān)閉頁(yè)面,而且也會(huì) 阻止計(jì)算機(jī)關(guān)閉(必須手動(dòng)強(qiáng)制退出); - 關(guān)閉頁(yè)面的服務(wù),如
websocket,下次打開窗口,窗口中的頁(yè)面會(huì) 重新渲染; - 通過(guò)這個(gè)
API觸發(fā)的close事件在unload和beforeunload之前觸發(fā),通過(guò)這點(diǎn)可以實(shí)現(xiàn) 關(guān)閉時(shí)觸發(fā)彈窗;

完整代碼在github:electron-playground
- 會(huì)被
closed事件捕捉到。
1.3.2. win.destroy()
- 強(qiáng)制退出,無(wú)視
close事件(即:無(wú)法通過(guò)event.preventDefault()來(lái)阻止); - 關(guān)閉頁(yè)面,以及頁(yè)面內(nèi)的服務(wù),下次打開窗口,窗口中的頁(yè)面會(huì)重新渲染;
- 會(huì)被
closed事件捕捉到。
1.3.3. win.hide()
這個(gè)隱藏窗口。
- 隱藏窗口,會(huì)觸發(fā)
hide和blur事件,同樣也是可以通過(guò)event.preventDefault()來(lái)阻止 - 只是隱藏窗口,通過(guò)
win.show(),可以將窗口顯現(xiàn),并且會(huì)保持原來(lái)的窗口,里面的服務(wù)也不會(huì)掛斷
2. 主窗口隱藏和恢復(fù)
2.1. 主窗口
2.1.1. 為什么需要 主窗口?
一個(gè)應(yīng)用存在著許多的窗口,需要一個(gè)窗口作為 主窗口,如果該窗口關(guān)閉,則意味著整個(gè)應(yīng)用被關(guān)閉。
場(chǎng)景:在應(yīng)用只有一個(gè)頁(yè)面的時(shí),用戶點(diǎn)擊關(guān)閉按鈕,不想讓整個(gè)應(yīng)用關(guān)閉,而是隱藏;
例如:其他的APP,像微信,QQ等桌面端。
利用上文中提到的關(guān)閉窗口的 API ,我們實(shí)現(xiàn)一個(gè)主窗口的隱藏和恢復(fù)。
改造一下 close 事件
let mainWindowId: number // 用于標(biāo)記主窗口id
const browserWindow = new BrowserWindow()
// 記錄下主窗口id
if (!mainWindowId) {
mainWindowId = browserWindow.id
}
browserWindow.on('close', event => {
// 如果關(guān)閉的是主窗口,阻止
if (browserWindow.id === mainWindowId) {
event.preventDefault()
browserWindow.hide()
}
})
2.1.2. 恢復(fù)主窗口顯示
能隱藏,就能恢復(fù)。
const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
mainWindow.restore()
mainWindow.show()
}
**mainWindow.show()** 方法:功能如其名,就是“show出窗口”。
_為什么要是有 __mainWindow.restore()_ ?
_windows_ _下如果 __hide_ _之后不調(diào)用 __show_ _方法而是只調(diào)用 __restore_ 方法就會(huì)導(dǎo)致頁(yè)面掛住不能用
2.1.3. 強(qiáng)制關(guān)閉主窗口
有些場(chǎng)景下,可能需要的強(qiáng)制退出,附上代碼:
const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
mainWindowId = -1
mainWindow.close()
}
存在的問(wèn)題
我們改變了
Electron窗口的既定行為,就會(huì)有許多場(chǎng)景下會(huì)有問(wèn)題
問(wèn)題一:因?yàn)樽柚沽?close 事件,導(dǎo)致 關(guān)機(jī) 時(shí)無(wú)法關(guān)閉 主窗口,可以使用如下代碼
app.on('before-quit', () => {
closeMainWindow()
})
在 macOS Linux Windows 下都可以。
問(wèn)題二:為避免啟動(dòng) 多個(gè)應(yīng)用
app.on('second-instance', () => {
const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
mainWindow.restore()
mainWindow.show()
}
})
在 macOS Linux Windows 下都可以
問(wèn)題三:首次啟動(dòng)應(yīng)用程序、嘗試在應(yīng)用程序已運(yùn)行時(shí)或單擊 應(yīng)用程序 的 塢站 或 任務(wù)欄圖標(biāo) 時(shí)重新激活它
app.on('activate', () => {
if (mainWindow) {
mainWindow.restore()
mainWindow.show()
}
})
只應(yīng)用于macOS 下
問(wèn)題四: 雙擊托盤圖標(biāo) 打開APP
tray.on('double-click', () => {
if (mainWindow) {
mainWindow.restore()
mainWindow.show()
}
})
這樣每個(gè)環(huán)節(jié)的代碼都有,即可實(shí)現(xiàn),具體代碼可參見(jiàn)鏈接
3. 窗口的聚焦和失焦
3.1. 聚焦
3.1.1. 創(chuàng)建窗口時(shí)配置
const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com')
focusable:true 窗口便可聚焦,便可以使用聚焦的 API
focusable:false 在 Windows 中設(shè)置 focusable: false 也意味著設(shè)置了skipTaskbar: true. 在 Linux 中設(shè)置 focusable: false 時(shí)窗口停止與 wm 交互, 并且窗口將始終置頂;
以下討論的情況僅為focusable:true情況下
const { BrowserWindow } = require('electron');
const win = new BrowserWindow() // focusable:true 為默認(rèn)配置
羅列了一下 API
3.1.2. 關(guān)于聚焦的API
| API | 功能 |
|---|---|
BrowserWindow.getFocusedWindow() |
來(lái)獲取聚焦的窗口 |
win.isFocused() |
判斷窗口是否聚焦 |
win.on('focus',handler) |
來(lái)監(jiān)聽(tīng)窗口是否聚焦 |
win.focus() |
手動(dòng)聚焦窗口 |
3.1.3. 其他API副作用和聚焦有關(guān)的:
| API | 功能 |
|---|---|
win.show() |
顯示窗口,并且聚焦于窗口 |
win.showInactive() |
顯示窗口,但是不會(huì)聚焦于窗口 |
3.2. 失焦
3.2.1. 關(guān)于失焦的api
| API | 功能 |
|---|---|
win.blur() |
取消窗口聚焦 |
win.on('blur',cb) |
監(jiān)聽(tīng)失焦 |
3.2.2. 其他api副作用和失焦有關(guān)的:
| api | 功能 |
|---|---|
win.hide() |
隱藏窗口,并且會(huì)觸發(fā)失焦事件 |
4. 窗口類型
4.1. 無(wú)邊框窗口
4.1.1. 描述
無(wú)邊框窗口是不帶外殼(包括窗口邊框、工具欄等),只含有網(wǎng)頁(yè)內(nèi)容的窗口
4.1.2. 實(shí)現(xiàn)
Windows macOS Linux
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ width: 800, height: 600, frame: false })
win.show()
在macOS下,還有不同的實(shí)現(xiàn)方式,官方文檔
4.1.3. macOS 下獨(dú)有的無(wú)邊框
- 配置
titleBarStyle: 'hidden'
返回一個(gè)隱藏標(biāo)題欄的全尺寸內(nèi)容窗口,在左上角仍然有標(biāo)準(zhǔn)的窗口控制按鈕(俗稱“紅綠燈”)
// 創(chuàng)建一個(gè)無(wú)邊框的窗口
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ titleBarStyle: 'hidden' })
win.show()
效果如下:

- 配置
titleBarStyle: 'hiddenInset'
返回一個(gè)另一種隱藏了標(biāo)題欄的窗口,其中控制按鈕到窗口邊框的距離更大。
// 創(chuàng)建一個(gè)無(wú)邊框的窗口
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })
win.show()
效果如下:

配置titleBarStyle: 'customButtonsOnHover'
效果如下:

4.1.4. 窗口頂部無(wú)法拖拽的問(wèn)題
雖然無(wú)邊框窗口,很美觀,可以自定義title;但是改變了Electron窗口頂部的默認(rèn)行為,就需要使用代碼來(lái)兼容它,實(shí)現(xiàn)其原來(lái)承擔(dān)的功能。

出現(xiàn)上述情況,是因?yàn)樵谀J(rèn)情況下, 無(wú)邊框窗口是不可拖拽的。 應(yīng)用程序需要在
CSS 中指定 -webkit-app-region: drag 來(lái)告訴 Electron 哪些區(qū)域是可拖拽的(如操作系統(tǒng)的標(biāo)準(zhǔn)標(biāo)題欄),在可拖拽區(qū)域內(nèi)部使用 -webkit-app-region: no-drag 則可以將其中部分區(qū)域排除。 請(qǐng)注意, 當(dāng)前只支持矩形形狀。完整文檔
使用-webkit-app-region: drag 來(lái)實(shí)現(xiàn)拖拽,但是會(huì)導(dǎo)致內(nèi)部的click事件失效。這個(gè)時(shí)候可以將需要click元素設(shè)置為-webkit-app-region: no-drag。具體的細(xì)節(jié) Electron 的issues
為了不影響窗口內(nèi)的業(yè)務(wù)代碼,這里拖拽的代碼,應(yīng)該在preload觸發(fā)。
preload代碼運(yùn)行,在窗口代碼運(yùn)行之前
核心代碼:
// 在頂部插入一個(gè)可以移動(dòng)的dom
function initTopDrag() {
const topDiv = document.createElement('div') // 創(chuàng)建節(jié)點(diǎn)
topDiv.style.position = 'fixed' // 一直在頂部
topDiv.style.top = '0'
topDiv.style.left = '0'
topDiv.style.height = '20px' // 頂部20px才可拖動(dòng)
topDiv.style.width = '100%' // 寬度100%
topDiv.style.zIndex = '9999' // 懸浮于最外層
topDiv.style.pointerEvents = 'none' // 用于點(diǎn)擊穿透
// @ts-ignore
topDiv.style['-webkit-user-select'] = 'none' // 禁止選擇文字
// @ts-ignore
topDiv.style['-webkit-app-region'] = 'drag' // 拖動(dòng)
document.body.appendChild(topDiv) // 添加節(jié)點(diǎn)
}
window.addEventListener('DOMContentLoaded', function onDOMContentLoaded() {
initTopDrag()
})
在創(chuàng)建窗口時(shí)引用 preload 即可
const path = require('path')
const { BrowserWindow } = require('electron')
const BaseWebPreferences = {
nodeIntegration: true,
preload: path.resolve(__dirname, './windowType.js'), // 這里引用preload.js 路徑
}
// 主窗口代碼
const win = new BrowserWindow({ webPreferences: BaseWebPreferences, frame: false, titleBarStyle: 'hiddenInset' })
win.loadURL('https://github.com')
便可實(shí)現(xiàn)窗口頂部拖拽

_tips: 如果窗口打開了 _
_devtools_ ,窗口也是可以拖拽的,只不過(guò)這個(gè)拖拽體驗(yàn)不好
4.2. 父子窗口
所謂的父子窗口,就是子窗口永遠(yuǎn)在父窗口之上,只要子窗口存在,哪怕位置不在父窗口上方,都是無(wú)法操作父窗口

const { BrowserWindow } = require('electron')
let top = new BrowserWindow()
let child = new BrowserWindow({ parent: top })
child.show()
top.show()
在 窗口之間通信 章節(jié)中介紹到父子窗口之間的通信;通過(guò) getParentWindow 拿到父窗口的 類BrowserWindowProxy,通過(guò) win.postMessage(message,targetOrigin) 實(shí)現(xiàn)通信
4.3. 模態(tài)窗口
模態(tài)窗口也是一種父子窗口,只不過(guò)展示會(huì)有不同
const { BrowserWindow } = require('electron')
let top = new BrowserWindow()
let child = new BrowserWindow({ parent: top, modal: true, show: false })
child.loadURL('https://github.com')
child.once('ready-to-show', () => {
child.show()
})

5. 窗口之間的通信
實(shí)現(xiàn)窗口通信必須不影響窗口內(nèi)的業(yè)務(wù)代碼,
jdk等的注入
5.1. 主進(jìn)程干預(yù)方式
主進(jìn)程是可以干預(yù)渲染進(jìn)程生成新的窗口的,只需要在創(chuàng)建窗口時(shí),webContents 監(jiān)聽(tīng) new-window
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),
}
// 創(chuàng)建窗口監(jiān)聽(tīng)
browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
event.preventDefault()
// 在通過(guò)BrowserWindow創(chuàng)建窗口
const win = new BrowserWindow({
show:false,
webPreferences: {
...BaseWebPreferences,
additionalArguments:[`--parentWindow=${browserWindow.id}`] // 把父窗口的id傳過(guò)去
}
});
win.loadURl(url);
win.once('ready-to-show',()=>{
win.show()
})
})
在 preload.js 文件window.process.argv,便能拿到父窗口的id,window.process.argv是一個(gè)字符串?dāng)?shù)組,可以使用yargs來(lái)解析
preload.js 代碼
import { argv } from 'yargs'
console.log(argv);

拿到了父窗口的
id ,封裝一下通信代碼,掛載到 window 上
/**
* 這個(gè)是用于窗口通信例子的preload,
* preload執(zhí)行順序在窗口js執(zhí)行順序之前
*/
import { ipcRenderer, remote } from 'electron'
const { argv } = require('yargs')
const { BrowserWindow } = remote
// 父窗口監(jiān)聽(tīng)子窗口事件
ipcRenderer.on('communication-to-parent', (event, msg) => {
alert(msg)
})
const { parentWindowId } = argv
if (parentWindowId !== 'undefined') {
const parentWindow = BrowserWindow.fromId(parentWindowId as number)
// 掛載到window
// @ts-ignore
window.send = (params: any) => {
parentWindow.webContents.send('communication-to-parent', params)
}
}
應(yīng)用一下試試看:

這種方法可以實(shí)現(xiàn)通信,但是太麻煩了。
5.2. 父子窗口通信
和主進(jìn)程干預(yù),通過(guò)ipc通信方式差不多,只是利用父子窗口這點(diǎn),不用通過(guò)additionalArguments傳遞父窗口id,在子窗口通過(guò)window.parent,就可以拿到父窗口
browserWindow.webContents.on('new-window', (event, url, frameName, disposition) => {
event.preventDefault()
// 在通過(guò)BrowserWindow創(chuàng)建窗口
const win = new BrowserWindow({
show:false,
webPreferences:BaseWebPreferences,
parent:browserWindow // 添加父窗口
});
win.loadURl(url);
win.once('ready-to-show',()=>{
win.show()
})
})
弊端:子窗口永遠(yuǎn)在父窗口之上。
const path = require('path')
const { BrowserWindow } = require('electron')
const BaseWebPreferences = {
// // 集成node
nodeIntegration: true,
// // 禁用同源策略
// webSecurity: false,
// 預(yù)加載腳本 通過(guò)絕對(duì)地址注入
preload: path.resolve(__dirname, './communication2.js'),
}
// 主窗口代碼
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) => {
// 阻止默認(rèn)事件
event.preventDefault()
// 在通過(guò)BrowserWindow創(chuàng)建窗口
// 子窗口代碼
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'),
)
})
preload.js
import { remote, ipcRenderer } from 'electron'
// 父窗口監(jiān)聽(tīng)子窗口事件
ipcRenderer.on('communication-to-parent', (event, msg) => {
alert(msg)
})
const parentWindow = remote.getCurrentWindow().getParentWindow()
// @ts-ignore
window.sendToParent = (params: any) =>
parentWindow.webContents.send('communication-to-parent', params)

但是必須得是父子窗口,有弊端。
5.3. 使用window.open
終極方法
在 web 端,使用 window.open 會(huì)返回一個(gè) windowObjectReference ,通過(guò)這個(gè)方法可以實(shí)現(xiàn) postMessage ;但是在 Electron 端,把 window.open 方法重新定義了;使用 window.open 創(chuàng)建一個(gè)新窗口時(shí)會(huì)返回一個(gè) BrowserWindowProxy對(duì)象,并提供一個(gè)有限功能的子窗口.
MDN文檔 Electron文檔
const BrowserWindowProxy = window.open('https://github.com', '_blank', 'nodeIntegration=no')
BrowserWindowProxy.postMessage(message, targetOrigin)
代碼精簡(jiǎn),且需要的功能,即符合 BrowserWindow(options) 中 options 配置的,都可以使用 window.open 配置。
6. 全屏、最大化、最小化、恢復(fù)
6.1. 全屏
6.1.1. 創(chuàng)建時(shí)進(jìn)入全屏
配置
new BrowserWindow({ fullscreen:true })
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ fullscreen:true,fullscreenable:true })
win.loadURL('https://github.com')
6.1.2. 使用API進(jìn)入全屏
確保當(dāng)前窗口的
fullscreenable:true,以下API才能使用
-
win.setFullScreen(flag),設(shè)置全屏狀態(tài); -
win.setSimpleFullScreen(flag),macOS下獨(dú)有,設(shè)置簡(jiǎn)單全屏。
6.1.3. 全屏狀態(tài)的獲取
-
win.fullScreen,來(lái)判斷當(dāng)前窗口是否全屏; -
win.isFullScreen(),macOS獨(dú)有; -
win.isSimpleFullScreen(),macOS獨(dú)有。
6.1.4. 全屏事件的監(jiān)聽(tīng)
-
rezise調(diào)整窗口大小后觸發(fā); -
enter-full-screen窗口進(jìn)入全屏狀態(tài)時(shí)觸發(fā); -
leave-full-screen窗口離開全屏狀態(tài)時(shí)觸發(fā); -
enter-html-full-screen窗口進(jìn)入由HTML API 觸發(fā)的全屏狀態(tài)時(shí)觸發(fā); -
leave-html-full-screen窗口離開由HTML API觸發(fā)的全屏狀態(tài)時(shí)觸發(fā)。
6.1.5. HTML API無(wú)法和窗口聯(lián)動(dòng)問(wèn)題
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'))
使用按鈕全屏和退出全屏是可以的,但是先點(diǎn)擊左上角??全屏,再使用按鈕退出全屏,是不行的。因?yàn)闊o(wú)法知道當(dāng)前的狀態(tài)是全屏,還是不是全屏。
解決辦法:,將win.setFullScreen(flag)方法掛載到窗口的window上
加載這樣一段preload.js代碼即可
import { remote } from 'electron'
const setFullScreen = remote.getCurrentWindow().setFullScreen
const isFullScreen = remote.getCurrentWindow().isFullScreen
window.setFullScreen = setFullScreen
window.isFullScreen = isFullScreen
_ setFullScreen文檔 https://www.electronjs.org/docs/api/browser-window#winsetfullscreenflag isFullScreen 文檔_https://www.electronjs.org/docs/api/browser-window#winisfullscreen
6.2. 最大化、最小化
6.2.1. 創(chuàng)建窗口配置
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')
當(dāng)使用 minWidth/maxWidth/minHeight/maxHeight 設(shè)置最小或最大窗口大小時(shí), 它只限制用戶。 它不會(huì)阻止您將不符合大小限制的值傳遞給 setBounds/setSize 或 BrowserWindow 的構(gòu)造函數(shù)。
6.2.2. 相關(guān)事件
| 事件名稱 | 觸發(fā)條件 |
|---|---|
maximize |
窗口最大化時(shí)觸發(fā) |
unmaximize |
當(dāng)窗口從最大化狀態(tài)退出時(shí)觸發(fā) |
minimize |
窗口最小化時(shí)觸發(fā) |
restore |
當(dāng)窗口從最小化狀態(tài)恢復(fù)時(shí)觸發(fā) |
6.2.3. 相關(guān)狀態(tài)API
-
win.minimizable窗口是否可以最小化 -
win.maximizable窗口是否可以最大化 -
win.isMaximized()是否最大化 -
win.isMinimized()是否最小化
6.2.4. 控制API
-
win.maximize()使窗口最大化 -
win.unmaximize()退出最大化 -
win.minimize()使窗口最小化 -
win.unminimize()退出最小化
6.3. 窗口恢復(fù)
win.restore() 將窗口從最小化狀態(tài)恢復(fù)到以前的狀態(tài)。在前面的例子 主窗口隱藏和恢復(fù)也有用到這個(gè)api
7. 窗口各事件觸發(fā)順序

7.1. 窗口加載時(shí)
BrowserWindow實(shí)例:即 new BrowserWindow() 返回的實(shí)例對(duì)象
webContents: 即 BrowserWindow 實(shí)例中的 webContents 對(duì)象
webPreferences: 即 new BrowserWindow(options) 中 options 的 webPreferences 配置對(duì)象
從上到下,依次執(zhí)行
| 環(huán)境 | 事件 | 觸發(fā)時(shí)機(jī) |
|---|---|---|
| webPreferences的preload | - | 在頁(yè)面運(yùn)行其他腳本之前預(yù)先加載指定的腳本 無(wú)論頁(yè)面是否集成Node, 此腳本都可以訪問(wèn)所有Node API 腳本路徑為文件的絕對(duì)路徑。 |
| webContents | did-start-loading |
當(dāng)tab中的旋轉(zhuǎn)指針(spinner)開始旋轉(zhuǎn)時(shí),就會(huì)觸發(fā)該事件 |
| webContents | did-start-navigation |
當(dāng)窗口開始導(dǎo)航是,觸發(fā)該事件 |
窗口中的JavaScript
|
DOMContentLoaded |
初始的 HTML 文檔被完全加載和解析完成 |
窗口中的JavaScript
|
load |
頁(yè)面資源全部加載完成之時(shí) |
BrowserWindow實(shí)例 |
show |
窗口顯示時(shí)觸發(fā)時(shí) |
webContents |
did-frame-navigate |
frame導(dǎo)航結(jié)束時(shí)時(shí) |
webContents |
did-navigate |
main frame導(dǎo)航結(jié)束時(shí)時(shí) |
BrowserWindow實(shí)例 |
page-title-updated |
文檔更改標(biāo)題時(shí)觸發(fā) |
webContents |
page-title-updated |
文檔更改標(biāo)題時(shí)觸發(fā) |
webContents |
dom-ready |
一個(gè)框架中的文本加載完成后觸發(fā)該事件 |
webContents |
did-frame-finish-load |
當(dāng)框架完成導(dǎo)航(navigation)時(shí)觸發(fā) |
webContents |
did-finish-load |
導(dǎo)航完成時(shí)觸發(fā),即選項(xiàng)卡的旋轉(zhuǎn)器將停止旋轉(zhuǎn) |
webContents |
did-stop-loading |
當(dāng)tab中的旋轉(zhuǎn)指針(spinner)結(jié)束旋轉(zhuǎn)時(shí),就會(huì)觸發(fā)該事件 |
7.2. 窗口加載完畢,用戶觸發(fā)事件(不包括resize和move)
| 事件 | 作用 |
|---|---|
page-title-updated |
文檔更改標(biāo)題時(shí)觸發(fā) |
blur |
當(dāng)窗口失去焦點(diǎn)時(shí)觸發(fā) |
focus |
當(dāng)窗口獲得焦點(diǎn)時(shí)觸發(fā) |
hide |
窗口隱藏 |
show |
窗口顯示 |
maximize |
窗口最大化時(shí)觸發(fā)(mac是雙擊title) |
unmaximize |
當(dāng)窗口從最大化狀態(tài)退出時(shí)觸發(fā) |
enter-full-screen |
窗口進(jìn)入全屏狀態(tài)時(shí)觸發(fā) |
leave-full-screen |
窗口離開全屏狀態(tài)時(shí)觸發(fā) |
enter-html-full-screen |
窗口進(jìn)入由HTML API 觸發(fā)的全屏狀態(tài)時(shí)觸發(fā) |
leave-html-full-screen |
窗口離開由HTML API觸發(fā)的全屏狀態(tài)時(shí)觸發(fā) |
always-on-top-changed |
設(shè)置或取消設(shè)置窗口總是在其他窗口的頂部顯示時(shí)觸發(fā)。 |
app-command |
window linux獨(dú)有 |
7.3. 用戶移動(dòng)窗口
- 移動(dòng)窗口之前
will-move; - 移動(dòng)窗口中
move; - 移動(dòng)之后
moved;
7.4. 用戶改變窗口大小
- 改變之前
will-resize; - 改變之后
resize
7.5. 窗口的內(nèi)容異常事件(webContent事件)
| 事件名 | 錯(cuò)誤類型 |
|---|---|
unresponsive |
網(wǎng)頁(yè)變得未響應(yīng)時(shí)觸發(fā) |
responsive |
未響應(yīng)的頁(yè)面變成響應(yīng)時(shí)觸發(fā) |
did-fail-load |
加載失敗,錯(cuò)誤碼 |
did-fail-provisional-load |
頁(yè)面加載過(guò)程中,執(zhí)行了window.stop()
|
did-frame-finish-load |
|
crashed |
渲染進(jìn)程崩潰或被結(jié)束時(shí)觸發(fā) |
render-process-gone |
渲染進(jìn)程意外失敗時(shí)發(fā)出 |
plugin-crashed |
有插件進(jìn)程崩潰時(shí)觸發(fā) |
certificate-error |
證書的鏈接驗(yàn)證失敗 |
preload-error |
preload.js拋出錯(cuò)誤 |
7.6. 窗口關(guān)閉(包括意外關(guān)閉)
- 關(guān)閉之前:觸發(fā)主進(jìn)程中注冊(cè)的
close事件 - 窗口內(nèi)的
JavaScript執(zhí)行window.onbeforeunload - 窗口內(nèi)的
JavaScript執(zhí)行window.onunload - 關(guān)閉之后:觸發(fā)主進(jìn)程中注冊(cè)的
closed事件
對(duì) Electron 感興趣?請(qǐng)關(guān)注我們的開源項(xiàng)目 Electron Playground,帶你極速上手 Electron。
我們每周五會(huì)精選一些有意思的文章和消息和大家分享,來(lái)掘金關(guān)注我們的 曉前端周刊。
我們是好未來(lái) · 曉黑板前端技術(shù)團(tuán)隊(duì)。
我們會(huì)經(jīng)常與大家分享最新最酷的行業(yè)技術(shù)知識(shí)。
歡迎來(lái) 知乎、掘金、Segmentfault、CSDN、簡(jiǎn)書、開源中國(guó)、博客園 關(guān)注我們。