发布于 2026-01-06 7 阅读
0

让 Electron 应用在 Mac 上拥有原生应用般的体验

让 Electron 应用在 Mac 上拥有原生应用般的体验

这是我之前在开源博客上发布的关于Lotus的文章——Lotus 是一款可以轻松管理 GitHub 通知的应用程序。希望大家喜欢!

当我开始考虑Lotus的时候,我就知道我会用Electron。开发者们喜欢开玩笑说运行任何 Electron 应用都需要 64GB 的内存,但我发现下面这个笑话更有趣:

我的应用占用0GB内存,因为我还没完成它。

我找不到那条推文了,但你应该明白我的意思。如果我当初决定用 Swift 开发原生应用,我估计会花好几个月时间不断失败,因为我对这门编程语言几乎一窍不通,最后只能放弃。

所以我选择了 Electron,并利用我现有的 Web 应用开发技能来创建一个 Mac 应用。我很庆幸自己做了这个决定,因为我只用了4 天就做出了一个可以运行的原型。

然而,尽管 Lotus 不是原生应用程序,但这并不意味着它不能像原生应用程序一样使用。

让 Electron 应用遵循与原生应用相同的标准和模式。我把目前为止我所了解的一切都写下来了,希望对其他 Electron 开发者有所帮助!

视窗

首次渲染

Electron 本质上是一个底层浏览器,因此在窗口创建后需要加载应用程序的所有 HTML、CSS 和 JavaScript 文件。这可能需要一些时间,这就是为什么 Electron 窗口会短暂显示空白的原因。

有一个小技巧可以让窗口在页面加载完毕后才显示:



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

const window = new BrowserWindow({
    show: false
});

window.once('ready-to-show', () => {
    window.show();
});


Enter fullscreen mode Exit fullscreen mode

应用此更改后,请查看效果并与上面的演示进行比较:

恢复窗口位置

当你移动窗口或调整窗口大小时,Lotus 会记住窗口的新位置和尺寸。下次启动 Lotus 时,窗口将位于上次的完全相同的位置,并且宽度和高度也保持不变。这虽然不易察觉,但用户已经习惯了原生应用程序的这种特性。

多亏了electron-window-state,在任何 Electron 应用中实现起来都相当容易。

可拖动区域

macOS 应用通常都有自定义标题栏,用户希望能够通过点击标题栏的空白区域来拖动整个窗口。

以下演示了如何通过点击应用程序顶部区域的任意位置来拖动窗口:

请注意,当我点击“收件箱”标签尝试拖动时,窗口并没有移动。这是一个需要注意的重要细节。

为了实现这些可拖动区域,我使用了两个 CSS 类:



.drag {
    -webkit-app-region: drag;
}

.no-drag {
    -webkit-app-region: no-drag;
}


Enter fullscreen mode Exit fullscreen mode

您可以.drag为整个标题栏容器元素添加一个类,并选择性地将该类添加.no-drag到需要阻止拖拽交互的元素上。例如:



<div class="drag">
    <h1 class="no-drag">Inbox</h1>
</div>


Enter fullscreen mode Exit fullscreen mode

用户界面

字体

我必须承认,我用了五个月才意识到 Lotus 里的文字比我用的其他所有应用里的都大。Lotus 的样式是由Tailwind驱动的,默认字体大小是 16px。这在网页上看起来还不错,但在桌面应用里就显得很突兀。

Sindre告诉我,原生应用的默认系统字体大小是13px,但在Lotus里看起来不太好,所以我折中用了14px。实际上,我现在更喜欢这个效果!

Tailwind 使用rem单位来定义其源代码中的所有尺寸,这让我可以通过添加一行代码来解决字体大小问题。



html {
  font-size: 14px;
}


Enter fullscreen mode Exit fullscreen mode

在 CSS 中,rem字体大小是相对于根字体大小计算的。所以,如果我指定1rem,浏览器会将其解释为14px,因为这是我上面为整个页面设置的字体大小。

此外,为了让你的 Electron 应用更好地兼容 macOS,请使用系统字体。Tailwind 默认会自动设置,但如果你不是 Tailwind 用户,以下是如何使用系统字体的方法:



html {
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont;
}


Enter fullscreen mode Exit fullscreen mode

光标

我几天前才发现这一点,是Sindre指出来的。原生应用即使是按钮和其他可点击元素,也使用默认光标(不是“手”形光标)。我完全忽略了这一点,因为我太习惯cursor: pointer在网页上设置交互元素的光标了。

这个问题也很容易解决:



*, a, button {
    cursor: default;
    user-select: none;
}


Enter fullscreen mode Exit fullscreen mode

指针(或“手形”)光标应该只用于指向应用程序外部的实际链接。

深色模式

这个功能无需过多介绍,但还有一个鲜为人知的技巧可以让 Electron 完美支持深色模式。不过,让我先描述一下问题所在。

Lotus 在深色模式下背景为深灰色,有一天我在调整其窗口大小时,注意到了这一点:

Electron窗口的默认背景色是白色。当我快速调整窗口大小时,Electron无法像原生应用那样快速地调整页面大小,导致页面出现白色背景闪烁,即使我的页面背景是灰色的。

要解决这个问题,请将窗口背景颜色设置为与页面使用的颜色相同。然后,​​每当系统切换到/退出深色模式时,都更新此颜色。



const {nativeTheme, BrowserWindow} = require('electron');

const darkBackgroundColor = 'black';
const lightBackgroundColor = 'white';

const window = new BrowserWindow({
    backgroundColor: nativeTheme.shouldUseDarkColors
        ? darkBackgroundColor
        : lightBackgroundColor
});

nativeTheme.on('updated', () => {
    const backgroundColor = nativeTheme.shouldUseDarkColors
        ? darkBackgroundColor
        : lightBackgroundColor;

    window.setBackgroundColor(backgroundColor);
});


Enter fullscreen mode Exit fullscreen mode

无论你以多快的速度调整窗口大小,都不会再看到白色背景的闪烁了。

未聚焦的 UI 元素变体

Lotus 拥有一个侧边栏导航,每个项目内都有彩色图标,当前选中的页面背景为亮紫色。当 Lotus 获得焦点时,所有颜色都会按原样显示:

但如果您点击离开或切换到其他应用程序,Lotus 就会失去焦点,并将颜色替换为灰色阴影:

这似乎是原生应用中另一个容易被忽略的小规律。它还需要在主进程和渲染进程中都编写代码才能正常工作。

在主进程中,你需要检测窗口何时获得焦点或失去焦点,并将这些事件传递给渲染进程。因为渲染进程本质上是一个浏览器,所以页面在其“眼中”永远不会失去焦点,因为它始终在 Electron 窗口中可见。



window.on('focus', () => {
    window.webContents.send('focus');
});

window.on('blur', () => {
    window.webContents.send('blur');
});


Enter fullscreen mode Exit fullscreen mode

然后,在渲染进程中,你需要使用ipcRenderer模块监听来自主进程的这些消息。



const {ipcRenderer} = require('electron');

ipcRenderer.on('focus', () => {
    // Change UI state to focused
});

ipcRenderer.on('blur', () => {
    // Change UI state to unfocused
});


Enter fullscreen mode Exit fullscreen mode

Lotus 是用 React 编写的,所以我把渲染器部分打包成了一个方便的useWindowFocushook,使用方法如下:



const isWindowFocused = useWindowFocus();

return <NavItem className={isWindowFocused ? 'bg-purple' : 'bg-gray'}></NavItem>;


Enter fullscreen mode Exit fullscreen mode

菜单

大多数 Mac 应用都有标准菜单,Electron 应用也应该有。

使用 Electron 提供的类可以轻松完成设置Menu。以下是一些实用链接,可帮助您快速入门并立即创建标准的 macOS 菜单:

我选择在 Lotus 中创建自定义菜单,因为我需要在其中添加很多自定义选项。这也引出了下一个技巧。

快捷方式

在 Web 应用中,键盘快捷键仍然比较少见,但在原生应用中,它们却是标配。在 Electron 中添加快捷键非常简单,所以你没有任何理由不添加!首先,添加一个自定义菜单项,然后使用accelerator属性配置一个触发该菜单项的快捷键。



{
    label: 'Refresh',
    accelerator: 'CmdOrCtrl+R',
    click: () => {
        // User clicked on the menu item or pressed ⌘R
    }
}


Enter fullscreen mode Exit fullscreen mode

乍一听,快捷方式需要菜单项才能生效可能有点奇怪,但请记住,用户通常会先浏览应用程序的菜单,然后才了解它有哪些快捷方式。

在 Lotus 中,我创建了一个单独的菜单部分,用于管理通知相关的操作,目前每个操作都分配了一个快捷方式:

撤销/重做

这是 Web 应用经常缺失的另一个功能。有趣的是,我们总是期望原生应用允许我们撤销或重做任何操作,但在 Web 应用中却没有同样的期望。无论如何,务必尽快将此功能添加到您的 Electron 应用中,它将显著提升您的原生应用体验。

撤销/重做功能开发起来很复杂,我不得不多次重写,但我认为我已经实现了一个足够抽象的版本,以后可以重用和开源。

偏好

我之前犯了个错误,把“首选项”页面和其他页面一样放在了侧边栏导航里,但现在 Lotus 已经把它设置成了一个独立的、类似原生界面的窗口。而且切换标签页的时候还有动画效果!绝对物超所值。

此外,也不需要向用户界面添加任何按钮来打开偏好设置窗口,因为所有 macOS 原生应用程序都遵循相同的模式,即向菜单中添加“偏好设置”项,并使用⌘,快捷键打开它。

坏消息是,我找不到任何可以快速创建偏好设置窗口的方法,所以你需要自己编写代码。

离线支持

除非你的应用绝对不能在没有网络连接的情况下运行,否则它应该在网络连接恢复后,通过同步更改来优雅地降级到离线优先模式。实际上,我已经几乎完成了 Lotus 的离线支持实现,尽管它依赖于来自 GitHub API 的外部数据。

以下是一些关于 Lotus 离线使用方法的提示:

  • 在连接建立之前,不会从 GitHub API 获取任何数据。
  • 由于 Lotus 甚至不会尝试获取新的通知,因此启动速度非常快。
  • 您仍然可以将通知标记为已读、稍后回复以及执行所有其他操作。Lotus 会将这些操作放入队列,并在您上线时按操作顺序将这些更改同步到 GitHub。
  • 嵌入式 github.com webview 显然无法正常工作,所以我改用备用 UI:

希望这篇关于 Electron UX 的深入探讨对您来说既有趣又有用!

还有哪些细节或模式可以让 Electron 应用感觉更像原生应用?我是否遗漏了什么?

文章来源:https://dev.to/vadimdemedes/making-electron-apps-feel-native-on-mac-52e8