创建支持内容切换及拖拽的悬浮窗,包含完整目录结构及示例代码。
目录结构
Text
1.
2├── main
3│ └── floating-window.js
4├── renderer
5│ ├── img
6│ │ └── logo.png
7│ ├── floating-window.html
8│ └── style.css
9├── sass
10│ ├── abstracts
11│ │ ├── _helpers.scss
12│ │ ├── _mixins.scss
13│ │ └── _variables.scss
14│ │── base
15│ │ └── _base.scss
16│ │── pages
17│ │ └── _floating-window.scss
18│ └── main.scss
19├── main.js
20├── package.json
21└── package-lock.json
NPM
package.json
Json
1{
2 "name": "floating-window",
3 "version": "1.0.0",
4 "description": "悬浮窗",
5 "main": "main.js",
6 "scripts": {
7 "start": "electron .",
8 "watch": "nodemon --exec electron .",
9 "compile:sass": "sass --no-source-map --watch sass/main.scss:renderer/style.css"
10 },
11 "keywords": [],
12 "author": "",
13 "license": "ISC",
14 "devDependencies": {
15 "electron": "^13.1.1",
16 "nodemon": "^2.0.7",
17 "sass": "^1.34.1"
18 }
19}
主进程
main.js
JavaScript
1const { app, BrowserWindow } = require('electron');
2
3const FloatingWindow = require('./main/floating-window');
4
5class Main {
6 floWin = new FloatingWindow();
7
8 constructor() {
9 app.whenReady().then(() => {
10 this._createWindow();
11 this._forMacOs();
12 });
13 }
14
15 _createWindow() {
16 this.floWin.resetWindow();
17 }
18
19 _forMacOs() {
20 app.on('activate', () => {
21 if (BrowserWindow.getAllWindows().length === 0) this._createWindow();
22 });
23
24 app.on('window-all-closed', () => {
25 if (process.platform !== 'darwin') app.quit();
26 });
27 }
28}
29
30new Main();
main/floating-window.js
JavaScript
1const { BrowserWindow, ipcMain } = require('electron');
2
3class FloatingWindow {
4 _width = 36;
5 _height = 36;
6 _min = true; // 是否是最小化的窗口尺寸
7 _maxWidth = 180;
8 _maxHeight = 180;
9 _regIpc = false;
10
11 win;
12
13 resetWindow() {
14 if (this.win && !this.win.isDestroyed()) return;
15
16 this._createWindow();
17 }
18
19 _createWindow() {
20 this.win = new BrowserWindow({
21 width: this._width,
22 height: this._height,
23 alwaysOnTop: true,
24 hasShadow: false,
25 transparent: true,
26 frame: false,
27 resizable: false,
28 skipTaskbar: true,
29
30 // 可避免一些在 Windows 中的 Bug,比如快捷键窗口切换时不再位于最上层等
31 type: 'toolbar',
32
33 // 将窗口背景设置与页面背景一致,避免闪烁
34 backgroundColor: '#2d2a2e',
35
36 webPreferences: {
37 nodeIntegration: true,
38 contextIsolation: false
39 }
40 });
41
42 // this.win.webContents.openDevTools();
43
44 this.win.loadFile('renderer/floating-window.html').then(() => {
45 if (this._regIpc) return;
46
47 this._regIpc = true;
48 this._toggleWindowSize();
49 });
50 }
51
52 _toggleWindowSize() {
53 ipcMain.handle('toggle-floating-window', async () => {
54 this._min = !this._min;
55
56 if (this._min) {
57 this.win.setSize(this._width, this._height);
58 } else {
59 this.win.setSize(this._maxWidth, this._maxHeight);
60 }
61
62 return this._min;
63 });
64 }
65}
66
67module.exports = FloatingWindow;
渲染器进程
renderer/floating-window.html
HTML
1<!DOCTYPE html>
2<html>
3 <head>
4 <meta charset="UTF-8">
5 <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
6 <link rel="stylesheet" href="style.css">
7 <title>悬浮窗</title>
8 </head>
9 <body style="-webkit-app-region: drag">
10 <div class="floating-window">
11 <img class="floating-window__logo" src="img/logo.png">
12 <div class="floating-window__tip hidden">双点切换</div>
13 </div>
14
15 <script>
16 const { ipcRenderer } = require('electron');
17
18 class Main {
19 containerEl = document.querySelector('.floating-window');
20 logoEl = document.querySelector('.floating-window__logo');
21 tipEl = document.querySelector('.floating-window__tip');
22
23 constructor() {
24 this._handleDoubleClick();
25 }
26
27 _handleDoubleClick() {
28 this.containerEl.addEventListener('dblclick', () => {
29 this._hideAll();
30
31 ipcRenderer.invoke('toggle-floating-window').then(min => {
32 if (min) {
33 this.logoEl.classList.remove('hidden');
34 } else {
35 this.tipEl.classList.remove('hidden');
36 }
37 });
38 });
39 }
40
41 _hideAll() {
42 !this.logoEl.classList.contains('hidden') && this.logoEl.classList.add('hidden');
43
44 !this.tipEl.classList.contains('hidden') && this.tipEl.classList.add('hidden');
45 }
46 }
47
48 new Main();
49 </script>
50 </body>
51</html>
样式(Sass)
sass/abstracts/_helpers.scss
Scss
1.hidden {
2 display: none !important;
3}
sass/abstracts/_mixins.scss
Scss
1@mixin no-drag {
2 -webkit-user-drag: none;
3}
4
5@mixin no-selection {
6 user-select: none;
7}
sass/abstracts/_variables.scss
Scss
1$color-primary: #2d2a2e;
sass/base/_base.scss
Scss
1@use '../abstracts/mixins' as *;
2
3*,
4*::after,
5*::before {
6 margin: 0;
7 padding: 0;
8 box-sizing: inherit;
9}
10
11html {
12 font-size: 10px / 16px * 100%; // 定义 1rem = 10px
13}
14
15body {
16 font: caption; // 系统字体
17 font-size: 1.6rem;
18 box-sizing: border-box;
19}
20
21// 不需要轮廓线
22input:focus,
23select:focus,
24textarea:focus,
25button:focus {
26 outline: none;
27}
28
29img {
30 // 图片不可拖拽
31 @include no-drag;
32}
sass/pages/_floating-window.scss
Scss
1@use '../abstracts/variables' as *;
2@use '../abstracts/mixins' as *;
3
4.floating-window {
5 height: 100vh;
6 background-color: $color-primary;
7
8 display: flex;
9 justify-content: center;
10 align-items: center;
11
12 &__logo {
13 display: inline-block;
14 width: 100%;
15 }
16
17 &__tip {
18 color: white;
19 @include no-selection;
20 }
21}
sass/main.scss
Scss
1@use 'abstracts/helpers';
2
3@use 'base/base';
4
5@use 'pages/floating-window';