其他
桌面端设计交互时:补齐 hover、指针精度、快捷键…
在 macOS、Windows、Linux 桌面或 Web 桌面环境设计 UI 时,按桌面惯例处理 hover、目标尺寸、快捷键、多窗口、焦点、信息密度与克制动效。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
桌面端设计交互时:补齐 hover、指针通用
解决思路
- 对版本敏感声明:API 名称、SDK gate、浏览器支持声明在 2026-09-08 已对照一手来源验证;查看 `_jutsu/VERSIONS.md`,若日期过旧,先复核再依赖版本号。
- Hover 是桌面主要可交互信号:每个可点击表面必须有独立 hover 样式,并加 100-200ms transition,使用户点击前确认元素可交互。
- CSS hover:为 `.button` 设置 `transition: background 120ms ease-out, transform 120ms ease-out`;`:hover` 设置 `background: var(--surface-hover)` 和 `transform: translateY(-1px)`;`:active` 设置 `transform: translateY(0)`。
- SwiftUI hover:用 `.onHover { Bool }` 作为可移植 hover 信号(iOS 13.4+、iPadOS 13.4+、Mac Catalyst 13.4+、macOS 10.15+、visionOS 1.0+);需要指针位置用 `.onContinuousHover { HoverPhase }`(iOS 17.0+、macOS 14.0+、tvOS 17.0+、visionOS 1.0+)。
- SwiftUI hover:`.hoverEffect(_:)` 在 iOS/iPadOS/Catalyst/tvOS/visionOS 可用但 macOS 不可用,在 macOS target 会编译错误,必须用 `#if` gate,不能用 `#available`;`.pointerStyle(_:)` 可用于 macOS 15.0+、visionOS 2.0+ 改变光标。
- SwiftUI hover 示例:用 `@State private var hovering = false`;给 Image 加 padding、背景 `hovering ? Color.gray.opacity(0.15) : .clear`、`.onHover { hovering = $0 }`、`.animation(.easeOut(duration: 0.12), value: hovering)`;用 `pointerHighlight()` extension:`#if os(macOS) self #else self.hoverEffect(.highlight) #endif`。
- Compose Desktop hover:用 `onPointerEvent` 或 `hoverable` + `interactionSource`;`remember { MutableInteractionSource() }`,`collectIsHoveredAsState()`,`Modifier.hoverable(interactionSource)`,hovered 时背景 `Color.LightGray.copy(alpha = 0.15f)`,否则 `Color.Transparent`,再 `.clickable(onClick = onClick)` 和 padding。
- 指针精度:鼠标/触控板比拇指精确,桌面目标可比 44pt 移动最小值更小;图标按钮常用 24-32px,工具栏项 28-36px。
- WCAG 2.5.8 Target Size (Minimum) AA:所有指针输入的底限是 24x24 CSS px,不是桌面专属;小于 24px 仍可走间距例外:在每个过小目标中心画 24px 直径圆,任意两圆不得相交。
- Apple macOS HIG 目标尺寸:默认控件 28x28 pt,绝对最小值 20x20 pt。
- Fitts 定律:目标获取时间随尺寸增大而缩短、随距离增大而增加;屏幕边缘和角落是无限深度目标,光标会停在那里;高频全局控件(关闭窗口、系统菜单、app dock)放角落和边缘;参考 macOS menubar 和 Windows taskbar 的边缘锚定、零 overshoot 获取。
- 键盘快捷键是一等公民:桌面用户期望与原生惯例一致;列表密集型 app 缺少 `⌘+F` 不是极简主义,是 bug。
- 提供标准快捷键映射:New `⌘+N`/`Ctrl+N`;Close window `⌘+W`/`Ctrl+W`;Quit app `⌘+Q`/`Alt+F4`;Settings/Preferences `⌘+,`/`Ctrl+,`;Find `⌘+F`/`Ctrl+F`;Toggle(comment、sidebar...)`⌘+/`/`Ctrl+/`;Save `⌘+S`/`Ctrl+S`;Command palette `⌘+K` 或 `⌘+Shift+P` / `Ctrl+K` 或 `Ctrl+Shift+P`。
- Web 检测 Ctrl vs Cmd:macOS 用 `event.metaKey`,其他平台用 `event.ctrlKey`;用 `navigator.userAgentData?.platform ?? navigator.platform ?? navigator.userAgent ?? ''` 配合 `/Mac|iPhone|iPad/` 判断 isMac;在 `keydown` 中 `const cmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;`,若 `cmdOrCtrl && e.key.toLowerCase() === 'k'` 则 `e.preventDefault(); openCommandPalette();`。
- SwiftUI 快捷键绑定到菜单命令:`Button('New Document', action: newDoc).keyboardShortcut('n', modifiers: .command)`;`Button('Find', action: focusSearch).keyboardShortcut('f', modifiers: .command)`。
- Compose Desktop 快捷键:用 `onKeyEvent + KeyShortcut`;定义 `Modifier.commandShortcut(key, onTrigger)`,在 `KeyEventType.KeyDown && event.isMetaPressed && event.key == key` 时触发并返回 true,否则 false;在 `MenuBar`/`Menu('File')` 中用 `Item('New', shortcut = KeyShortcut(Key.N, meta = true), onClick = ::newDoc)` 和 `Item('Find', shortcut = KeyShortcut(Key.F, meta = true), onClick = ::focusSearch)`。
- 多窗口:桌面用户会并排使用窗口;以下情况新开窗口:任务运行较久,用户想继续在主窗口工作(渲染、导出、同步日志);用户比较两个平行上下文(两个文档、聊天、issue);app 是文档型且每个文档是对等窗口(Pages、Figma files、Xcode projects)。
- 多窗口:瞬时确认、简短设置面板、可放在 sheet 或 popover 的内容不应新开窗口。
- SwiftUI 多窗口:文档型用 `WindowGroup('Document') { DocumentView() }`;单例用 `Window('Inspector', id: 'inspector') { InspectorView() }.windowResizability(.contentSize)`;设置用 `Settings { SettingsView() }`(macOS 目标绑定 `⌘+,`)。
- Compose Desktop 多窗口:在 `application { ... }` 中管理窗口;用 `remember { mutableStateListOf(Document()) }`,对每个 doc 创建 `Window(onCloseRequest = { docs.remove(doc) }, title = doc.title) { DocumentView(doc) }`;用 `if (showInspector) Window(onCloseRequest = { showInspector = false }, title = 'Inspector') { InspectorView() }`。
- 多窗口状态共享:窗口是同一模型的视图;source of truth 放在单例或 DI-scoped object(SwiftUI `@Observable` 经 environment 注入,Compose 用 `koin` 或 `viewModel` equivalent);绝不为每个窗口复制状态。
- 焦点管理:键盘导航是桌面一等输入;tab 顺序必须合理,焦点环必须可见,移除焦点环而不提供替代是 accessibility regression。
- SwiftUI 焦点:用 `@FocusState` 驱动字段焦点;示例 `enum Field { case email, password }`、`@FocusState private var focused: Field?`、TextField `.focused($focused, equals: .email).onSubmit { focused = .password }`、SecureField `.focused($focused, equals: .password).onSubmit(submit)`、`.onAppear { focused = .email }`。
- Compose 焦点:用 `FocusRequester + LocalFocusManager`;创建 `emailFocus`、`passwordFocus`、`focusManager`;第一个 TextField 用 `Modifier.focusRequester(emailFocus)`、`keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)`、`keyboardActions = KeyboardActions(onNext = { passwordFocus.requestFocus() })`;第二个用 `Modifier.focusRequester(passwordFocus).focusable()`、`keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus(); submit() })`;`LaunchedEffect(Unit) { emailFocus.requestFocus() }`;注意没有 `keyboardOptions.imeAction` 时 IME 不会发出 Next,`onNext` 不会触发。
- Web 焦点:用 `tabindex + :focus-visible`;不要在没有替代时写 `outline: none`;`.button:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }`;用 `tabindex='0'` 把非交互元素放入 tab 顺序,如 `<div role='button' tabindex='0' class='button'>Custom button</div>`。
- 信息密度:桌面用户有 13-32 英寸屏幕、精确指针和完整键盘,能且希望每视口解析比移动端更多信息;使用 8px 基础网格(移动端 4-8px)、持久侧边栏而非底部 tab、面向高级用户的 command palette(`⌘K`)、必要时使用密集数据表;参考 Linear、Things 3、Notion:信息丰富但不拥挤,每个像素都有价值。
- 细微动画原则:桌面 app 会被盯着数小时;一次觉得愉悦的动画第 100 次会难以忍受;优先短小、纯功能性动效:opacity 和小位移、低于 200ms、日常交互不要 bounce、hover 不要俏皮 overshoot;表现性动效留给一次性时刻(onboarding、success states),不要用于日常 UI。
- 动画 BAD/GOOD:BAD 是 `.card { transition: transform 600ms cubic-bezier(0.34, 1.56, 0.64, 1); } .card:hover { transform: scale(1.05); }`;GOOD 是 `.card { opacity: 0.92; transition: opacity 100ms ease-out; } .card:hover { opacity: 1; }`。
- 反模式 1:不要在桌面把导航藏在 hamburger 后;1440px 视口有无限空间,不该折叠 nav;GOOD 用持久 sidebar,用户想折叠时再提供 collapse-toggle。
- 反模式 2:不要没有主操作快捷键;不要把 New 埋在菜单里导致每次创建 3 次点击;GOOD 用 `⌘N` 基线,在菜单中呈现,在 toolbar tooltip 中镜像;全局绑定 `useShortcut('mod+n', newDoc)`、`useShortcut('mod+s', save)`、`useShortcut('mod+f', focusSearch)`。
- 反模式 3:不要移除焦点环而不提供替代;BAD `button:focus { outline: none; }`;GOOD `button:focus { outline: none; } button:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; border-radius: 6px; }`。
- 按需加载子技能:键盘模式深入加载 `references/keyboard-patterns.md`;多窗口状态加载 `references/multi-window.md`;SwiftUI 动画加载 `../swiftui-motion/SKILL.md`;Compose Desktop 加载 `../compose-multiplatform/SKILL.md`(当 desktop 是 CMP target 时)。
适用边界 · 注意事项
- 不要用于移动端/触摸优先界面:hover 是桌面的反向信号,44pt 移动最小目标等约定不适用。
- 不要为瞬时确认、简短设置面板或可用 sheet/popover 承载的内容新开窗口。
- 不要对日常高频 UI 使用夸张、弹跳或 overshoot 动画;表现性动效只留给 onboarding、success 等一次性时刻。