本文主要講解Electron
窗口的 API
和一些在開發(fā)之中遇到的問題哑姚。
官方文檔 雖然比較全面祭饭,但是要想開發(fā)一個(gè)商用級別的桌面應(yīng)用必須對整個(gè) Electron API
有較深的了解,才能應(yīng)對各種需求蜻懦。
1. 創(chuàng)建窗口
通過BrowserWindow
甜癞,來 創(chuàng)建 或者 管理 新的瀏覽器窗口,每個(gè)瀏覽器窗口都有一個(gè)進(jìn)程來管理宛乃。
1.1. 簡單創(chuàng)建窗口
const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com');
效果如下:
1.1.2. 優(yōu)化
問題:electron
的 BrowserWindow
模塊在創(chuàng)建時(shí)悠咱,如果沒有配置 show:false
,在創(chuàng)建之時(shí)就會(huì)顯示出來征炼,且默認(rèn)的背景是白色析既;然后窗口請求 HTML
,會(huì)出現(xiàn)視覺閃爍谆奥。
解決
const { BrowserWindow } = require('electron');
const win = new BrowserWindow({ show:false });
win.loadURL('https://github.com');
win.on('ready-to-show',()=>{
win.show();
})
兩者對比有很大的區(qū)別
1.2. 管理窗口
所謂的管理窗口眼坏,相當(dāng)于主進(jìn)程可以干預(yù)窗口多少。
- 窗口的路由跳轉(zhuǎn)
- 窗口打開新的窗口
- 窗口大小酸些、位置等
- 窗口的顯示
- 窗口類型(無邊框窗口宰译、父子窗口)
- 窗口內(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
對象來存儲(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)聽新窗口創(chuàng)建
// 創(chuàng)建窗口監(jiān)聽
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)過實(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)程訪問 * *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)致的安全問題
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)聽
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ā)脯丝。
// 窗口注冊close事件
win.on('close',(event)=>{
event.preventDefault() // 阻止窗口關(guān)閉
})
closed
事件在窗口關(guān)閉后出觸發(fā)商膊,但是此時(shí)的窗口已經(jīng)被關(guān)閉了,無法通過 event.preventDefault()
來阻止窗口關(guān)閉宠进。
win.on('closed', handler)
主進(jìn)程能夠關(guān)閉窗口的 API
有很多晕拆,但都有各自的利弊。
1.3.1. win.close()
關(guān)于這個(gè) API
的利弊
- 如果當(dāng)前窗口實(shí)例注冊并阻止
close
事件材蹬,將不會(huì)關(guān)閉頁面实幕,而且也會(huì) 阻止計(jì)算機(jī)關(guān)閉(必須手動(dòng)強(qiáng)制退出); - 關(guān)閉頁面的服務(wù)堤器,如
websocket
昆庇,下次打開窗口,窗口中的頁面會(huì) 重新渲染吼旧; - 通過這個(gè)
API
觸發(fā)的close
事件在unload
和beforeunload
之前觸發(fā)凰锡,通過這點(diǎn)可以實(shí)現(xiàn) 關(guān)閉時(shí)觸發(fā)彈窗;
- 會(huì)被
closed
事件捕捉到圈暗。
1.3.2. win.destroy()
- 強(qiáng)制退出掂为,無視
close
事件(即:無法通過event.preventDefault()
來阻止); - 關(guān)閉頁面员串,以及頁面內(nèi)的服務(wù)勇哗,下次打開窗口,窗口中的頁面會(huì)重新渲染寸齐;
- 會(huì)被
closed
事件捕捉到欲诺。
1.3.3. win.hide()
這個(gè)隱藏窗口抄谐。
- 隱藏窗口,會(huì)觸發(fā)
hide
和blur
事件扰法,同樣也是可以通過event.preventDefault()
來阻止 - 只是隱藏窗口蛹含,通過
win.show()
,可以將窗口顯現(xiàn)塞颁,并且會(huì)保持原來的窗口浦箱,里面的服務(wù)也不會(huì)掛斷
2. 主窗口隱藏和恢復(fù)
2.1. 主窗口
2.1.1. 為什么需要 主窗口?
一個(gè)應(yīng)用存在著許多的窗口,需要一個(gè)窗口作為 主窗口祠锣,如果該窗口關(guān)閉酷窥,則意味著整個(gè)應(yīng)用被關(guān)閉。
場景:在應(yīng)用只有一個(gè)頁面的時(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ù)留特。