构建跨平台Electron应用4个必知的小技巧

原文地址:4 must-know tips for building cross platform Electron apps

在其他众多的应用中,Electron技术驱动着Avocode(译者注:一款设计师和开发者的合作工具),Electron可以让你构建一个运行非常快速流畅的跨平台桌面应用。

If you don’t pay attention you will quickly end up in the uncanny valley of apps, though. Apps that don’t feel quite in place among your other apps.(如果有些问题不注意的话,你的App很快就会掉到“坑”里,无法从其它众多App中脱颖而出。)

这是2016年5月在阿姆斯特丹的Electron Meetup上我做的发言的整理稿内容,由于要考虑到API的变化所以这里做了一些更新。值得注意的是,我这里会做一些更加详细的叙述,并且假设你们对Electron有了一些了解。

首先,我是谁?

我是Kilian Valkhof,一个前端开发者、交互设计师、app开发者,当然这取决于你问的对象是谁了。我有超过10年的互联网从业经验,并且在各式各样的环境下开发出很多的桌面应用,比如GTKQt,当然了它们都是使用的Electron。

我最近开发的应用是Fromscratch,它是一款跨平台免费的带有自动保存功能的记笔记应用。

在开发Fromscratch期间,我花了大量时间去确保这款应用在所有三大平台上都能保持良好的运行且可以感觉到体验很棒,并找出了可以用Electron来实现它的方法。这些都是我挖坑填坑过程中积累起来的。

让一款使用Electron开发的应用体验起来很好并且很稳定并不难,而你仅仅需要去确定你关注到的是它的细节。

1. Copy and paste on macOS 在macOS上复制粘贴

默认情况下,Mac系统下在Electron中复制和粘贴命令是不生效的!

让用户在你的应用内和外部使用复制和粘贴功能实现的方式是通过提供一个包括这两个命令的应用菜单。在你的main.js文件中简单的添加如下代码将可以实现很好体验的复制粘贴功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
if (process.platform === 'darwin') {
var template = [{
label: 'FromScratch',
submenu: [{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click: function() { app.quit(); }
}]
}, {
label: 'Edit',
submenu: [{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
selector: 'undo:'
}, {
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
selector: 'redo:'
}, {
type: 'separator'
}, {
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
selector: 'cut:'
}, {
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
selector: 'copy:'
}, {
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
selector: 'paste:'
}, {
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:'
}]
}];
var osxMenu = menu.buildFromTemplate(template);
menu.setApplicationMenu(osxMenu);
}

如果你已经有了菜单,那么你需要将上述剪切/复制/粘贴命令添加到你的已有菜单中。

1.1 指定一个图标

在Ubuntu上你的app会看起来像这样:

不幸的是很多的应用有这个问题,因为在Windows系统和macOS系统中,图标是在任务栏或者是dock中的并且显示的图标就是应用图标(一个.ico或者.icns)。而在Ubuntu系统上显示的却是你的窗口图标,添加它非常简单,在你的BrowserWindow中声明一个图标就可以了:

1
2
3
4
mainWindow = new BrowserWindow({
title: 'ElectronApp',
icon: __dirname + '/app/assets/img/icon.png',
};

这样也可以在你的Windows应用中左上角显示一个小图标。

1.2 UI文本是不可以被选中的

当你使用你的浏览器,word软件,或者其他任何一个原生应用,你可能注意到了你不能选中菜单上的任何文本和其他应用中的文本比如Chrome。Electron app的其中一个方式是在可能会被选中的元素上触发文本选中或者高亮。

CSS在这里可以帮助我们:向所有按钮,菜单,或者其它任何UI元素,添加下面的代码:

1
2
3
.my-ui-text {
-webkit-user-select: none;
}

这样文本将不再会被选中了,这样它感觉上去更像一个原生应用了。一个最简单的检测它的小技巧:在你的应用中按下ctrl/cmd + A选中所有可选择的文本,然后你可以快速的点击你不希望被选中的文本,这样可以有助于你快速识别哪些还需要添加这个效果。

1.3 你需要为三个平台提供三种图标

说实在的,这点真的是相当不方便啊,在Windows系统上你需要一个.ico文件,在macOS上你需要一个.icns文件,在Linux上你需要一个.png文件。

幸运的是,同一个普通的png可以用来生成其他两个图标,下面应该是最简单的方式了:

  • 1.准备一张1024x1024像素的PNG图片,这意味着我们已经做好了三种平台的一种(Linux)

  • 2.对于Windows,可以通过icotools生成一个.ico

1
icontool -c icon.png > icon.ico
  • 3.对于macOS,可以通过png2icns生成一个.icns
1
png2icns icon.icns icon.png
  • 4.你已经做完了!

当然这里也有可使用的GUI工具,比如说macOS上的img2icns以及web、Windows、macOS上的iconverticons,当然我还没有使用过它们。

1.4 Bonus! 意外之喜!

electron-packager不需要额外的icon来为给定的平台选择正确的图标:

1
electron-packager . MyApp --icon=img/icon --platform=all --arch=all --version=0.36.0 --out=../dist/ --asar

当然了,我仅仅是在写完自己的构建脚本之后选择icon正确的版本之后才发现的。

2. 在浏览器中的白屏加载

一个Electron应用在特定浏览器中什么也不用呈现而不是一块白屏的加载,幸运的是这里有我们可以做的两件事情可以避免它:

2.1 指定BrowserWindow的背景色

如果你的应用是没有白色背景的,那么确定在BrowserWindow选项中指定它。这并不会阻止应用加载时的纯色方块,但至少它不会半路改变颜色:

1
2
3
4
mainWindow = new BrowserWindow({
title: 'ElectronApp',
backgroundColor: '#002b36'
};

2.2 隐藏你的应用直到页面加载完成

因为应用实际上是在浏览器中运行的,我们可以选择在所有资源加载完成前隐藏窗口。在开始前,确保隐藏掉浏览器窗口:

1
2
3
4
var mainWindow = new BrowserWindow({
title: 'ElectronApp',
show: false,
};

然后当所有内容都加载完成时,显示窗口并聚焦在上面提醒用户。这里推荐使用 BrowserWindow的”ready-to-show”事件实现,或者用webContents的”did-finish-load”事件。

1
2
3
4
mainWindow.on('ready-to-show', function() {
mainWindow.show();
mainWindow.focus();
});

这里你应该调用foucs方法来提醒用户你的应用已经加载完成了。

3. 保持窗口的大小和位置

这个问题在很多原生应用中也存在,我发现这是最令人头疼的事情之一。本来一个位置处理很好的app在重启时所有的位置又变为默认的了,虽然这对于开发者来说是很合理的,但这会让人有种想撞墙的冲动,千万不要这样做。

相反的,保存窗口的大小和位置,并在每次重启时恢复,这样你的用户会很感激的。

3.1 Prebuilt solutions 预编译方案

这里有electron-window-stateelectron-window-state-manager两种预编译方案来为你解决这个问题。两种方案都能用,好好阅读文档并且注意边界情况,比如最大化你的应用。如果你很想快一点编译完成并看到成品,你可以采用这两种方案。

3.2 Roll your own 自行处理滚动

你可以自己处理滚动,这也正是我用的方案,主要是基于我前几年给Trimage写的代码的基础上实现的。并不需要写很多的代码,而且可以给你很多控制权。下面是演示:

3.2.1 把状态保存起来

首先我们得把应用的位置和大小保存在某个地方。你可以用Electron-settings可以轻松做到这一点,但我选择用node-localstorage因为它更简单。

1
2
3
var JSONStorage = require('node-localstorage').JSONStorage;
var storageLocation = app.getPath('userData');
global.nodeStorage = new JSONStorage(storageLocation);

如果你把数据保存到getPath('userData');,electron将会把它保存到自己的应用设置里,在~/.config/YOURAPPNAME位置,在Windows上就是你的用户文件夹下的appdata文件夹中。

3.2.2 打开应用时恢复你的状态
1
2
3
4
5
6
var windowState = {};
try {
windowState = global.nodeStorage.getItem('windowstate');
} catch (err) {
// the file is there, but corrupt. Handle appropriately.
}

当然了,第一次启动的时候是不可行的,因此你需要处理这种情况。或许可以通过提供默认设置。一旦你在JavaScript对象中获取到了前一次的状态,就使用保存的状态信息去设置BrowserWindow的大小:

1
2
3
4
5
6
7
var mainWindow = new BrowserWindow({
title: 'ElectronApp',
x: windowState.bounds && windowState.bounds.x || undefined,
y: windowState.bounds && windowState.bounds.y || undefined,
width: windowState.bounds && windowState.bounds.width || 550,
height: windowState.bounds && windowState.bounds.height || 450,
});

正如你看到的,我通过提供回调值来添加默认设置。

现在在Electron中,在开启应用时并不能以最大化状态启动应用,因此我们得在创建好BrowserWindow之后再最大化窗口。

1
2
3
4
5
// Restore maximised state if it is set.
// not possible via options so we do it here
if (windowState.isMaximized) {
mainWindow.maximize();
}
3.2.3 在move、resize和close时保存当前状态

在理想情况下你只需要在关闭应用时保存你的窗口状态,但事实上它错过了很多未知原因导致的应用终止事件,比如断电之类的。

在每次moveresize事件时获取和保存状态可以让我们可以恢复上次已知状态的位置和大小。

1
2
3
4
5
['resize', 'move', 'close'].forEach(function(e) {
mainWindow.on(e, function() {
storeWindowState();
});
});

storeWindowState方法:

1
2
3
4
5
6
7
8
var storeWindowState = function() {
windowState.isMaximized = mainWindow.isMaximized();
if (!windowState.isMaximized) {
// only update bounds if the window isn't currently maximized
windowState.bounds = mainWindow.getBounds();
}
global.nodeStorage.setItem('windowstate', windowState);
};

storeWindowState函数有个小小的问题:如果你最小化一个最大化状态的原生窗口时,它会恢复到前一个状态,这意味着本来我们想要保存的是最大化的状态,但我们并不想覆盖掉前一个窗口的大小(没有最大化的窗口),因此如果你最大化,关闭,重新打开,取消最大化,这时应用的位置是你最大化之前的位置。

4. 一些小技巧

下面是一些很小很简短有用的小技巧。

4.1 Shortcuts 快捷键

通常来讲Windows和Linux使用Ctrl,而macOS用Cmd。为了避免给每个快捷键(在Electron这叫做加速器Accelerator)添加两次,你可以用 “CmdOrCtrl”一次性给所有的平台进行设置。

4.2 使用系统字体San Francisco

使用系统默认的字体意味着你的应用可以和操作系统看起来很和谐。为了避免给每个系统都单独设置字体,你可以用下面的CSS代码块速实现更随系统字体:

1
2
3
body {
font: caption;
}

“caption”是CSS中的关键字,它会连接到系统指定字体。

4.3 系统颜色

和系统字体一样,你也可以用System colors让系统决定你应用的颜色。这其实是一个在CSS3中已经弃用的未完全实现的属性,但在可见的未来中它并不会被很快废弃。

4.4 布局

CSS是个相当强大的布局方式,尤其是把calc()和flexbox结合到一起时,但这并不会减少在像GTK,Qt或者Apple Autolayout这类老旧的GUI框架中需要做的工作。你可以用Grid Stylesheets(译者注:这是一个基于约束的布局系统)采用类似的方式实现你app的GUI。

感谢!

在Electron中构建应用是一件很有趣的事情并且会让你有很多的收获: 你可以在很短的时间内实现并运行一个跨平台的应用。如果你之前从没有用过Electron我希望这篇文章可以引起你足够的兴趣去尝试它。他们的网站有很全的文档以及很多很酷的Demo可以让你尝试它的API。

如果你已经在写Electron应用了,我希望上面的可以鼓励你更多的考虑你的app在所有平台上究竟运行的怎么样。

最后,如果你还有什么其他的小技巧,请把它分享在评论区里。

坚持原创技术分享,您的支持将鼓励我继续创作!