1. Raycast 的一篇 blog 和 Raycast v2
在今年 5 月,Raycast 这个 macOS 的工具台,也是我一直喜欢的软件,即将推出公开测试版。在此之前我就一直看到在 Twitter 上有很多人分享他们的 Alpha 测试图片,让人看得心痒痒。我也想加入测试,但是貌似需要工作邮箱和身份证明,总之门槛较高,所以放弃。
然而 Raycast v2 推出 Public Beta 的时候让我有点奇怪,我发现 RAM 占用相较于过去翻了一倍不止,即使是 spare time 占用也超过了 400MB,这十分夸张。要知道即使是 GPUI 构建的跨平台代码编辑器工具 Zed,在打开一个不太大的项目时,RAM 占用也就是 400MB 的水平,Raycast v1 一般在 100MB 到 200MB 之间。这让我有点不好的预感,果不其然,我发现我一直忽略了一个小小的按钮:Download for Windows。
要知道,Raycast v1 可是 Swift 和 AppKit 写的,想用 Apple 攥得死死的这套技术构建跨平台工具基本等于作死。
当然,理论上其实是支持的。Swift 5.3 在 2020 年 9 月 16 日发布;随后 Swift 官方在 9 月 22 日正式宣布 Swift on Windows,提供 Windows 原生工具链。The Browser Company 就使用 Swift 构建了 Windows 版本的 Arc 浏览器,至于性能嘛...
于是我开始猜测 Raycast v2 使用的技术栈,Qt?GPUI?难不成真是 Swift on Windows?
当我读到博客的这一行时,我心凉了大半截:
这让我们来到了 混合方法。事实证明,构建我们自己的原生外壳来封装系统 WebView 能够满足我们的所有需求。在 macOS 上,我们拥有一个完整的 Xcode 项目;在 Windows 上,我们拥有一个 Visual Studio 项目。我们可以完全访问平台 API,使用系统自带的 WebView 来渲染 UI,并能完全控制各个组件之间的通信方式。为了验证这种方法是否可行,我们早期构建了一个原型。我们能否实现半透明窗口?能否在 WebView 内容之上显示原生工具提示?它看起来和用起来会像 Raycast 吗?原型最终看起来与原生应用几乎一模一样。半透明的 WebView 与窗口背景融为一体,原生覆盖层用于显示工具提示和操作面板等功能。本质上,这与我们花费数年时间构建的视觉语言相同。
好家伙,看来 RAM 占用高可不是啥「早期版本的优化问题」了,看来这个问题是别想解决了,Raycast 为了兼容 Microslop 公司的 Windows 系统,也不得不坐上了「轮椅」啊…
2. 言归正传
Raycast 的博客里有两句话耐人寻味:
交互式控件上不使用
cursor: pointer。桌面应用不会这样做。虽然这很细微,但它会立刻发出「这是一个网站」的信号。
大多数控件上没有悬停高亮。在 macOS 上,按钮和列表项在悬停时不会像网页那样高亮显示。
看到这两句话的时候,我开始理解了什么叫工程师和设计师的思维,不过这两条内容竟然在 X 上引发了讨论,有的人说:「悬浮效果从小手改为指针就叫原生了?」还有的人说把「精装修」的 Web 应用当作原生工具简直是诈骗。
我开始深挖这些内容,发现牵扯到的设计思路比我想象的复杂得多。
3. Apple 的设计取向
Apple 在 macOS 上面的软件竟然真的很少有 hover 效果,会给人一种「死板」的感觉,明明 hover 效果让人的交互感很强,并且会给人一种「软件还活着,没死」的感觉,为什么 Apple 认为 hover 没啥用呢?
在 Apple 的开发者文档和人机交互指南里调查一番后,我错了。
事实上 Apple 并不认为 hover 没用,而是认为 hover 应该提供额外信息,而不是成为「这是可点击的」这一事实的主要证明。
4. 状态和意图
macOS 原生 App 里很少出现 Web 那种「鼠标移上去 → 背景变色 / 缩放 / 发光」的普遍反馈。Apple 的 HIG(人机交互指南)把 hover 定义成一种可以添加 scale、tint、shadow 等视觉变化的效果,但它属于可选增强,并没有要求所有交互元素都响应 hover。SwiftUI 也提供了 onHover、onContinuousHover、hoverEffect 等 API,说明 Apple 显然支持它,只是没有把它变成所有控件的默认视觉行为。
我觉得这里最有意思的是 Apple 对 「状态」 和 「意图」 的区分:
pressed:用户已经采取行动 → 应该明确反馈。selected:对象处于某种持久状态 → 应该明确反馈。focused:接下来操作的目标发生变化 → 有必要反馈。Apple 的 HIG 也明确把 focus 描述为帮助确认交互目标。hovered:鼠标刚好经过 → 用户可能根本没有操作意图。
所以 macOS 经常宁愿保持界面静止。
例如 Finder sidebar、System Settings、Xcode 里的大量 row/button,你移动鼠标经过时,经常什么都不发生;点击、选中之后才出现明确状态。这会产生一种很特别的感觉:
UI 不会一直追着鼠标动。
而 Web UI 的传统恰好不同。网页历史上需要不断回答:
这玩应能不能点啊?
于是 :hover 成为很重要的 affordance:链接变色、卡片升起、按钮换背景、cursor 变化。
但是 macOS 原生控件本身已经有非常稳定的视觉语法——toolbar button、sidebar row、segmented control、checkbox、menu item 等等,Apple 更愿意让控件形态本身表达可操作性,减少第二层 hover 动画。
这也能解释 SwiftUI 一个很有意思的 API 设计:Button 并不会因为你在 macOS 上创建了它,就自动获得 Web 风格的 .background(...) hover。你真想做,可以非常直接地:
@State private var hovering = false
Button("Open") {
// ...
}
.padding(6)
.background(hovering ? .quaternary : .clear)
.onHover { hovering = $0 }Apple 给了开发者 onHover,甚至还有连续位置的 onContinuousHover,但视觉语义留给开发者决定。
还有一个很能体现 Apple 思路的地方:Apple 对 iPad pointer 反而投入了非常大量的 hover 设计。它甚至设计了 pointer morph、lift、highlight 等效果;SwiftUI 的 HoverEffect 也有 automatic、highlight、lift 等概念。
原因很微妙:iPad 原本是 touch UI,引入鼠标之后,pointer 需要帮助用户理解「触摸目标在哪里」;而 Mac 从诞生开始就是 pointer-first,很多控件已经不需要额外解释。
看来这个 Raycast 的开发团队有点东西啊。