【Electron Playground 系列】窗口篇

作者:Kurosaki

本文主要讲解Electron 窗口的 API 和一些在开发之中遇到的问题。

官方文档 虽然比较全面,但是要想开发一个商用级别的桌面应用必须对整个 Electron API 有较深的了解,才能应对各种需求。

1. 创建窗口

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

1.1. 简单创建窗口

const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com');

效果如下:

open-windows.gif

1.1.2. 优化

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

解决

const { BrowserWindow } = require('electron');
const win = new BrowserWindow({ show:false });

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

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

两者对比有很大的区别


window-shows.gif

1.2. 管理窗口

所谓的管理窗口,相当于主进程可以干预窗口多少。

  • 窗口的路由跳转
  • 窗口打开新的窗口
  • 窗口大小、位置等
  • 窗口的显示
  • 窗口类型(无边框窗口、父子窗口)
  • 窗口内 JavaScriptnode 权限,预加载脚本等
  • ....

这些个方法都存在于BrowserWindow模块中。

1.2.1. 管理应用创建的窗口

BrowserWindow模块在创建窗口时,会返回 窗口实例,这些 **窗口实例 **上有许多功能方法,我们利用这些方法,管理控制这个窗口。

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

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中的实例删除。

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

1.2.2. 管理用户创建的窗口

主进程可以控制窗口许多行为,这些行为会在后续文章一一列举;以下以主进程控制窗口建立新窗口的行为为例。

使用new-window监听新窗口创建

// 创建窗口监听
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 都能捕捉到的。

以下方式打开的窗口可以被new-window事件捕捉到

window.open('https://github.com')
<a href='https://github.com' target='__blank'>链接</a>

**
渲染进程中使用BrowserWindow创建新窗口,不会被 new-window事件捕捉到
**

const { BrowserWindow } = require('electron').remote
const win = new BrowserWindow()
win.loadURL('https://github.com')

_渲染进程访问 __remote_ _,主进程需配置enableRemoteModule:true _
使用这种方式同样可以打开一个新的窗口,但是主进程的new-window捕捉不到。

应用new-window
new-window 控制着窗口新窗口的创建,我们利用这点,可以做到很多事情;比如链接校验、浏览器打开链接等等。默认浏览器打开链接代码如下:

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

_关于 __shell_ 模块,可以查看官网 https://www.electronjs.org/docs/api/shell
_

1.3. 关闭窗口

**close** **事件和 ****closed** 事件
close 事件在窗口将要关闭时之前触发,但是在 DOMbeforeunloadunload 事件之前触发。

// 窗口注册close事件
win.on('close',(event)=>{
    event.preventDefault()  // 阻止窗口关闭
})

closed 事件在窗口关闭后出触发,但是此时的窗口已经被关闭了,无法通过 event.preventDefault() 来阻止窗口关闭。

win.on('closed', handler)

主进程能够关闭窗口的 API 有很多,但都有各自的利弊。

1.3.1. win.close()

关于这个 API 的利弊

  1. 如果当前窗口实例注册并阻止close事件,将不会关闭页面,而且也会 阻止计算机关闭(必须手动强制退出);
  2. 关闭页面的服务,如websocket,下次打开窗口,窗口中的页面会 重新渲染
  3. 通过这个API触发的close事件在 unloadbeforeunload之前触发,通过这点可以实现 关闭时触发弹窗

window-close.gif

完整代码在github:electron-playground

  1. 会被closed事件捕捉到。

1.3.2. win.destroy()

  1. 强制退出,无视close事件(即:无法通过event.preventDefault()来阻止);
  2. 关闭页面,以及页面内的服务,下次打开窗口,窗口中的页面会重新渲染;
  3. 会被closed事件捕捉到。

1.3.3. win.hide()

这个隐藏窗口。

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

2. 主窗口隐藏和恢复

2.1. 主窗口

2.1.1. 为什么需要 主窗口?

一个应用存在着许多的窗口,需要一个窗口作为 主窗口,如果该窗口关闭,则意味着整个应用被关闭。
场景:在应用只有一个页面的时,用户点击关闭按钮,不想让整个应用关闭,而是隐藏;
例如:其他的APP,像微信,QQ等桌面端。

利用上文中提到的关闭窗口的 API ,我们实现一个主窗口的隐藏和恢复。

改造一下 close 事件

let mainWindowId: number // 用于标记主窗口id

const browserWindow = new BrowserWindow()

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

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

2.1.2. 恢复主窗口显示

能隐藏,就能恢复。

const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
  mainWindow.restore()
  mainWindow.show()
}

**mainWindow.show()** 方法:功能如其名,就是“show出窗口”。
_为什么要是有 __mainWindow.restore()_
_windows_ _下如果 __hide_ _之后不调用 __show_ _方法而是只调用 __restore_ 方法就会导致页面挂住不能用

2.1.3. 强制关闭主窗口

有些场景下,可能需要的强制退出,附上代码:

const mainWindow = BrowserWindowsMap.get(mainWindowId)
if (mainWindow) {
  mainWindowId = -1
  mainWindow.close()
}

存在的问题

我们改变了 Electron 窗口的既定行为,就会有许多场景下会有问题

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

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

在 macOS Linux Windows 下都可以。

问题二:为避免启动 多个应用

app.on('second-instance', () => {
  const mainWindow = BrowserWindowsMap.get(mainWindowId)
  if (mainWindow) {
    mainWindow.restore()
    mainWindow.show()
  }
})

在 macOS Linux Windows 下都可以

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

app.on('activate', () => {
  if (mainWindow) {
    mainWindow.restore()
    mainWindow.show()
  }
})

只应用于macOS

问题四: 双击托盘图标 打开APP

tray.on('double-click', () => {
  if (mainWindow) {
    mainWindow.restore()
    mainWindow.show()
  }
})

这样每个环节的代码都有,即可实现,具体代码可参见链接

3. 窗口的聚焦和失焦

3.1. 聚焦

3.1.1. 创建窗口时配置

const { BrowserWindow } = require('electron');
const win = new BrowserWindow();
win.loadURL('https://github.com')

focusable:true 窗口便可聚焦,便可以使用聚焦的 API
focusable:falseWindows 中设置 focusable: false 也意味着设置了skipTaskbar: true. 在 Linux 中设置 focusable: false 时窗口停止与 wm 交互, 并且窗口将始终置顶;

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

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

罗列了一下 API

3.1.2. 关于聚焦的API

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

3.1.3. 其他API副作用和聚焦有关的:

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

3.2. 失焦

3.2.1. 关于失焦的api

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

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

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

4. 窗口类型

4.1. 无边框窗口

4.1.1. 描述

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

4.1.2. 实现

Windows macOS Linux

const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ width: 800, height: 600, frame: false })
win.show()

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

4.1.3. macOS 下独有的无边框

  • 配置titleBarStyle: 'hidden'

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

// 创建一个无边框的窗口
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ titleBarStyle: 'hidden' })
win.show()

效果如下:


window-type-frame.gif
  • 配置titleBarStyle: 'hiddenInset'

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

// 创建一个无边框的窗口
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })
win.show()

效果如下:


window-type-frame2.gif
配置titleBarStyle: 'customButtonsOnHover'

效果如下:


window-type-frame3.gif

4.1.4. 窗口顶部无法拖拽的问题

虽然无边框窗口,很美观,可以自定义title;但是改变了Electron窗口顶部的默认行为,就需要使用代码来兼容它,实现其原来承担的功能。

window-type1.gif

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

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

为了不影响窗口内的业务代码,这里拖拽的代码,应该在preload触发。

preload 代码运行,在窗口代码运行之前

核心代码:

// 在顶部插入一个可以移动的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()
})

在创建窗口时引用 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')

便可实现窗口顶部拖拽

window-type.gif

_tips: 如果窗口打开了 __devtools_ ,窗口也是可以拖拽的,只不过这个拖拽体验不好

4.2. 父子窗口

所谓的父子窗口,就是子窗口永远在父窗口之上,只要子窗口存在,哪怕位置不在父窗口上方,都是无法操作父窗口

window-type2.gif
const { BrowserWindow } = require('electron')

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

窗口之间通信 章节中介绍到父子窗口之间的通信;通过 getParentWindow 拿到父窗口的 类BrowserWindowProxy,通过 win.postMessage(message,targetOrigin) 实现通信

4.3. 模态窗口

模态窗口也是一种父子窗口,只不过展示会有不同

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()
})
window-type3.gif

5. 窗口之间的通信

实现窗口通信必须不影响窗口内的业务代码, jdk 等的注入

5.1. 主进程干预方式

主进程是可以干预渲染进程生成新的窗口的,只需要在创建窗口时,webContents 监听 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),
}


// 创建窗口监听
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传过去
      } 
    });
    win.loadURl(url);
    win.once('ready-to-show',()=>{
        win.show()
    })
})

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

preload.js 代码

import { argv } from 'yargs'
console.log(argv);

yargv-parse.png

拿到了父窗口的 id ,封装一下通信代码,挂载到 window

/**
 * 这个是用于窗口通信例子的preload,
 * preload执行顺序在窗口js执行顺序之前
 */
import { ipcRenderer, remote } from 'electron'
const { argv } = require('yargs')

const { BrowserWindow } = remote

// 父窗口监听子窗口事件
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)
  }
}

应用一下试试看:


window-com.gif

这种方法可以实现通信,但是太麻烦了。

5.2. 父子窗口通信

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

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

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

const path = require('path')
const { BrowserWindow } = require('electron')

const BaseWebPreferences = {
  // // 集成node
  nodeIntegration: true,
  // // 禁用同源策略
  // webSecurity: false,
  // 预加载脚本 通过绝对地址注入
  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) => {
  // 阻止默认事件
  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'),
  )
})

preload.js

import { remote, ipcRenderer } from 'electron'

// 父窗口监听子窗口事件
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)

window-com1.gif

但是必须得是父子窗口,有弊端。

5.3. 使用window.open

终极方法

web 端,使用 window.open 会返回一个 windowObjectReference ,通过这个方法可以实现 postMessage ;但是在 Electron 端,把 window.open 方法重新定义了;使用 window.open 创建一个新窗口时会返回一个 BrowserWindowProxy对象,并提供一个有限功能的子窗口.
MDN文档 Electron文档

const  BrowserWindowProxy = window.open('https://github.com', '_blank', 'nodeIntegration=no')
BrowserWindowProxy.postMessage(message, targetOrigin)

代码精简,且需要的功能,即符合 BrowserWindow(options)options 配置的,都可以使用 window.open 配置。

6. 全屏、最大化、最小化、恢复

6.1. 全屏

6.1.1. 创建时进入全屏

配置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进入全屏

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

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

6.1.3. 全屏状态的获取

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

6.1.4. 全屏事件的监听

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

6.1.5. HTML API无法和窗口联动问题

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
加载这样一段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. 创建窗口配置

完整API文档

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 的构造函数。

6.2.2. 相关事件

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

6.2.3. 相关状态API

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

6.2.4. 控制API

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

6.3. 窗口恢复

win.restore() 将窗口从最小化状态恢复到以前的状态。在前面的例子 主窗口隐藏和恢复也有用到这个api

7. 窗口各事件触发顺序

window-event.png

7.1. 窗口加载时

BrowserWindow实例:即 new BrowserWindow() 返回的实例对象
webContents: 即 BrowserWindow 实例中的 webContents 对象
webPreferences: 即 new BrowserWindow(options)optionswebPreferences 配置对象

从上到下,依次执行

环境 事件 触发时机
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)结束旋转时,就会触发该事件

7.2. 窗口加载完毕,用户触发事件(不包括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独有

7.3. 用户移动窗口

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

7.4. 用户改变窗口大小

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

7.5. 窗口的内容异常事件(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抛出错误

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

  • 关闭之前:触发主进程中注册的 close 事件
  • 窗口内的 JavaScript 执行 window.onbeforeunload
  • 窗口内的 JavaScript 执行 window.onunload
  • 关闭之后:触发主进程中注册的 closed 事件

对 Electron 感兴趣?请关注我们的开源项目 Electron Playground,带你极速上手 Electron。

我们每周五会精选一些有意思的文章和消息和大家分享,来掘金关注我们的 晓前端周刊


我们是好未来 · 晓黑板前端技术团队。
我们会经常与大家分享最新最酷的行业技术知识。
欢迎来 知乎掘金SegmentfaultCSDN简书开源中国博客园 关注我们。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容