文章目录

  • 一、目标原型
  • 1. 目标
  • 2. 原型设计
  • 3. 原型初步实现
  • 二、无边框窗口
  • 1. 要点
  • 2. 改造
  • 三、可拖拽区
  • 1. 要点
  • 2. 改造
  • 四、最小化、最大化、关闭
  • 1. 要点
  • 2. 改造
  • 五、动态改变窗口大小
  • 1. 要点
  • 2. 改造
  • 六、扩展
  • 扩展1:在内嵌的独立项目中怎么实现拖拽、最大化、最小化、关闭?
  • 扩展2:在``标签内嵌的页面中怎么实现拖拽、最大化、最小化、关闭?
  • 扩展3:如何在Electron关闭时执行cmd命令
  • 扩展4:系统托盘tray
  • 七、源码下载


一、目标原型

1. 目标
  • 实现一个无边框窗口,包括最小化、最大化、关闭、拖动等功能
  • 动态改变窗口大小,即在页面跳转的时候根据需要改变窗口大小
2. 原型设计

(1) 登录窗口(500×300)

  • 点击登录跳转到首页

(2) 首页(全屏)

  • 跳转到首页,自动全屏
  • 包括最小化、最大化、关闭,以及灰色区域可拖动整个窗口功能
3. 原型初步实现

创建login.html和index.html页面,实现原型页面基本效果,下文逐步改造,实现Electron无边框窗口。

二、无边框窗口

1. 要点

要创建无边框窗口,只需在BrowserWindow的options中将frame设置为 false:

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

通过将 transparent 选项设置为 true, 还可以使无框窗口透明:
let win = new BrowserWindow({ transparent: true, frame: false }) 参考地址:https://electronjs.org/docs/api/frameless-window#%E9%80%8F%E6%98%8E%E7%AA%97%E5%8F%A3

2. 改造

main.js文件中添加frame:false

mainWindow = new BrowserWindow({
  width: 800,
  height: 600,
  frame:false,
  webPreferences: {
    /*preload: path.join(__dirname, 'preload.js')*/
    nodeIntegration: true
  }
})

注意: webPreferences的值,如果写preload无nodeIntegration,会影响后面最大最小化功能不起作用,暂不明白原因。

三、可拖拽区

1. 要点
  • 应用程序需要在 CSS 中指定 -webkit-app-region: drag 来告诉 Electron 哪些区域是可拖拽的
  • 在可拖拽区域内部使用 -webkit-app-region: no-drag 则可以将其中部分区域排除
  • 拖动行为可能与选择文本冲突。 例如, 当您拖动标题栏时, 您可能会意外地选择标题栏上的文本。 为防止此操作, 您需要在可区域中禁用文本选择
.titlebar {
  -webkit-app-region: drag;
  -webkit-user-select: none;
}
  • 在某些平台上,可拖拽区域不被视为窗口的实际内容,而是作为窗口边框处理,因此在右键单击时会弹出系统菜单。 要使上下文菜单在所有平台上都正确运行, 您永远也不要在可拖拽区域上使用自定义上下文菜单
  • 备注:如果你在某些页面设置了可拖拽区,跳转到一个新的页面,而这个新的页面没有设置可拖拽区,则会沿用上一页面的可拖拽区,感觉很奇怪,也没有相应的dom支持,就像取用的上一页面固定的像素区域一样(测试发现是这样,不准确之处请指正,谢谢)。如果拖拽区域不同,这种情况下,在新的页面中设置上拖拽区域即可。
2. 改造

修改index.html页面中的样式,添加相应drag和no-drag特性

<div class="topbar" style="-webkit-app-region: drag;-webkit-user-select: none;">
    <div class="logo fl">
        <img src="images/logo.png" alt="">
        <span>这里写软件名称</span>
    </div>
    <ul class="menu fl" style="-webkit-app-region: no-drag;">
        <li>菜单1</li>
        <li>菜单2</li>
        <li>菜单3</li>
        <li>菜单4</li>
    </ul>
    <div class="min_max_close fr" style="-webkit-app-region: no-drag;">
        <img src="images/min.png" id="min" alt="">
        <img src="images/max.png" id="max" alt="">
        <img src="images/close.png" id="close" alt="">
    </div>
</div>

四、最小化、最大化、关闭

1. 要点
  • render 进程通过 ipcRenderer 与 ipcMain 进行通讯,以通知 main 进程操作窗体。
2. 改造

(1) 在renderer.js中添加click事件,发送操作命令给主进程

let ipcRenderer = require('electron').ipcRenderer;

var max = document.getElementById('max');
if (max) {
    max.addEventListener('click', () => {
        //发送最大化命令
        ipcRenderer.send('window-max');
        //最大化图形切换
        if (max.getAttribute('src') == 'images/max.png') {
            max.setAttribute('src', 'images/maxed.png');
        } else {
            max.setAttribute('src', 'images/max.png');
        }
    })
}

var min = document.getElementById('min');
if (min) {
    min.addEventListener('click', () => {
        //发送最小化命令
        ipcRenderer.send('window-min');
    })
}

var close = document.getElementById('close');
if (close) {
    close.addEventListener('click', () => {
        //发送关闭命令
        ipcRenderer.send('window-close');
    })
}

(2) 在main.js中接收操作命令,做出相应处理

let ipcMain = require('electron').ipcMain;
//接收最小化命令
ipcMain.on('window-min', function() {
    mainWindow.minimize();
})
//接收最大化命令
ipcMain.on('window-max', function() {
    if (mainWindow.isMaximized()) {
        mainWindow.restore();
    } else {
        mainWindow.maximize();
    }
})
//接收关闭命令
ipcMain.on('window-close', function() {
    mainWindow.close();
})

补充: 这里最大化和取消最大化时,修改图标的方法不合适,因为通过双击drag-area或者拉动窗口到屏幕边缘等操作,也可能修改窗口的状态。
因此,应该在主进程中监听窗口的最大化操作,然后发送命令给渲染进程:

mainWindow.on('maximize', function () {
 mainWindow.webContents.send('main-window-max');
})
mainWindow.on('unmaximize', function () {
  mainWindow.webContents.send('main-window-unmax');
})

在渲染进程中接收到相应命令,再进行处理:

ipcRenderer.on('main-window-max', (event) => {
	max.classList.remove('icon-max');
	max.classList.add('icon-maxed');
});
ipcRenderer.on('main-window-unmax', (event) => {
	max.classList.remove('icon-maxed');
	max.classList.add('icon-max');
});

五、动态改变窗口大小

1. 要点

思路同最小化、最大化、关闭,仅添加了页面调转。

2. 改造

(1) 在main.js中设置启动初始页面为login.html

mainWindow.loadFile('login.html')

(2) 在renderer.js中添加login按钮的click事件,发送最大化给主进程

var loginbtn = document.getElementById('login');
if (loginbtn) {
    loginbtn.addEventListener('click', () => {
        ipcRenderer.send('window-max');
        location.href = "index.html";
    })
}

六、扩展

扩展1:在内嵌的独立项目中怎么实现拖拽、最大化、最小化、关闭?

如果你只是用Electron打个包,用它的浏览器功能,里面还是一个完整的其它项目,比如一个普通的java web项目,相当于只是穿层衣服。怎么在里边实现以上最小化、最大化、关闭、拖动等功能呢?

  1. 首先创建一个完全独立的nodejs项目,创建一个index页面,其中有最小化、最大化、关闭按钮和拖拽区
  2. 在Electron项目index.html中直接跳转到独立项目页面
<script type="text/javascript">
window.location='http://localhost:8888/index.html';
</script>
  1. 实现可拖拽区
    同上加上样式即可:style="-webkit-app-region: drag;-webkit-user-select: none;"
  2. 实现最小化、最大化、关闭
    关键的有两步:
    (1)在main.js中new BrowserWindow加上nodeIntegration: true
mainWindow = new BrowserWindow({
    width: 500,
    height: 300,
    frame: false,
    webPreferences: {
        nodeIntegration: true
    }
})

(2)在独立项目中加上之前的renderer.js文件,在页面中引入renderer.js

<script type="text/javascript" src="renderer.js"></script>

其实不应该这样做,应该可以直接写require('./renderer.js')的,但是这样要求在electron项目中把renderer.js export出来,由于我语法不熟,就直接拷过去了,先不研究了。

扩展2:在<webview>标签内嵌的页面中怎么实现拖拽、最大化、最小化、关闭?

在webview标签上加上nodeintegration属性:

<webview src="http://www.google.com/" nodeintegration></webview>

当有此属性时, webview 中的访客页(guest page)将具有Node集成, 并且可以使用像 require 和 process 这样的node APIs 去访问低层系统资源。

扩展3:如何在Electron关闭时执行cmd命令

两种实现方式:我的需求比较简单,简单实现如下:

//接收关闭命令
ipcMain.on('window-close', function() {
	let closeProcess = child_process.exec('taskkill /f /im XXXXX.exe');
	closeProcess.on('close', function (code) {
		mainWindow.close();
	})
})
扩展4:系统托盘tray

在任务栏右下角添加图标和右键菜单。查看Electron tray文档即可。