mcpField
M
mobile-principles

mobile-principles

github.com/athevon设计与 UI698970 次安装3 周前更新
代码仓库

关于 mobile-principles

面向移动端 Web、iOS 和 Android 开发的触控优先设计技能,提供触控目标、安全区域、手势、无障碍和性能方面的实践规则。适合 Claude Code、claude.ai 和 Cowork 工作流。

主要功能

  1. 01触控目标尺寸与间距建议
  2. 02触屏交互与悬停处理
  3. 03拇指可达区域布局规则
  4. 04安全区域与系统边距处理
  5. 05减少动态效果的跨平台方法
  6. 06手势和性能实践指南

适用场景

  1. 01检查移动界面的触控可用性
  2. 02设计适配刘海与系统栏的布局
  3. 03为动画添加减少动态效果支持
  4. 04选择移动端常见手势交互

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/athevon/genjutsu --skill mobile-principles

安装后在 Claude Code 中输入/mobile-principles确认已加载。

详细介绍

版本敏感。 以下每个 API 名称、SDK 门槛和浏览器支持声明均已于 2026-09-08 对照一手来源核实。核对了哪些内容,以及核对时间,见 _jutsu/VERSIONS.md。如果该日期已久,请在依据版本号采取行动前重新核实。

移动端原则

触控优先的 UX 背景信息。检测到移动端(移动版 Web、iOS、Android)时加载。 此处列出简明规则。深入内容见 references/。


触控目标

平台推荐尺寸(按此设计)硬性下限间距规范
Apple iOS / iPadOS44x44 pt28x28 pt带边框控件周围约 12 pt 内边距,无边框控件周围约 24 ptApple HIG,辅助功能 > 移动性
Apple watchOS44x44 pt28x28 pt同上Apple HIG
Android48x48 dp48x48 dp目标之间间隔 8 dpMaterial Design 3
Web 移动端44x44 CSS px24x24 CSS px不重叠的间距为 24 px 时,符合 AA 例外要求WCAG 2.5.5 目标尺寸(增强),AAA = 44x44;WCAG 2.5.8 目标尺寸(最低),AA = 24x24

**经验法则:**任何小于平台最低尺寸的点击目标都属于可用性缺陷,仅此而已。 命中区域可以超出可见图形(使用内边距、hitSlop 或透明的内部间隔元素),但可交互表面必须达到最低尺寸。间距和尺寸同样重要:两个边缘相接的 44pt 按钮仍然很容易误触。


不依赖悬停原则

触控设备上不存在 :hover。将其作为主要触发方式,意味着每部手机上都会有隐藏的操作入口。任何只能通过悬停触发的功能,在移动端都等于消失。默认显示是原则;悬停样式只能作为桌面端的增强,绝不能成为关键交互。

CSS - 将悬停样式限制在媒体查询中:

.card { opacity: 1; transform: translateY(0); }

@media (hover: hover) and (pointer: fine) {
  .card { opacity: 0.85; }
  .card:hover { opacity: 1; transform: translateY(-2px); }
}

SwiftUI - 使用轻点和长按,不使用伪悬停:

Image(systemName: "heart")
  .onTapGesture { toggleLike() }
  .contextMenu {
    Button("Share", systemImage: "square.and.arrow.up", action: share)
    Button("Report", systemImage: "flag", role: .destructive, action: report)
  }

Compose - 使用 combinedClickable 处理轻点和长按:

Box(
  modifier = Modifier
    .combinedClickable(
      onClick = { toggleLike() },
      onLongClick = { showContextMenu() },
    )
) {
  Icon(Icons.Default.Favorite, contentDescription = "Like")
}

拇指区域(Hoober)

Steven Hoober 的研究表明,用户通常单手或将手机托在手中使用竖屏手机,拇指以底部角落为支点活动。屏幕可划分为以下可触及区域:

+------+----+------+
| HARD | OK | HARD |   <- top: stretch, two-handed only
+------+----+------+
|  OK  | OK |  OK  |   <- middle: comfortable
+------+----+------+
| EASY |EASY| EASY |   <- bottom: natural thumb arc
+------+----+------+
  • **底部三分之一(EASY):**主要 CTA、发送、确认、FAB、标签栏。
  • **中间(OK):**内容、次要操作。
  • **顶部(HARD):**返回、关闭、搜索、个人资料——用户预期会主动去点,而不会下意识点击的内容。

**规则:**主要 CTA 放在下半屏。次要、不常用或具有破坏性的操作放在顶部。绝不要把“付款”按钮放在手机屏幕的右上角。


安全区域

平台API需要考虑的内边距
Web`env(safe-area-inset-topright
SwiftUI.safeAreaInset(edge: ...)、safeAreaInsets 环境变量导航栏、标签栏、刘海、主屏幕
ComposeModifier.windowInsetsPadding(WindowInsets.safeDrawing)系统栏、IME、挖孔区域

Web:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
.fab {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom) + 16px);
  right: calc(env(safe-area-inset-right) + 16px);
}

SwiftUI:

ScrollView { content }
  .safeAreaInset(edge: .bottom) {
    PrimaryCTA().padding()
  }

Compose:

Column(
  modifier = Modifier
    .fillMaxSize()
    .windowInsetsPadding(WindowInsets.safeDrawing)
) { /* content */ }

减少动态效果(跨平台统一)

平台API
Web CSS@media (prefers-reduced-motion: reduce)
Web JSwindow.matchMedia('(prefers-reduced-motion: reduce)')
SwiftUI@Environment(\.accessibilityReduceMotion) var reduceMotion
UIKitUIAccessibility.isReduceMotionEnabled
Compose使用 Settings.Global.ANIMATOR_DURATION_SCALE 的自定义辅助函数(见下方代码;深入内容见 references/accessibility-mobile.md)

SwiftUI:

struct Hero: View {
  @Environment(\.accessibilityReduceMotion) var reduceMotion
  @State private var shown = false

  var body: some View {
    Text("Welcome")
      .opacity(shown ? 1 : 0)
      .offset(y: shown ? 0 : (reduceMotion ? 0 : 20))
      .animation(reduceMotion ? .none : .easeOut(duration: 0.3), value: shown)
      .onAppear { shown = true }
  }
}

UIKit:

let duration = UIAccessibility.isReduceMotionEnabled ? 0 : 0.3
UIView.animate(withDuration: duration) {
  view.alpha = 1
  view.transform = .identity
}

Compose(辅助函数模式):

@Composable
fun rememberReduceMotion(): Boolean {
  val context = LocalContext.current
  return remember {
    Settings.Global.getFloat(
      context.contentResolver,
      Settings.Global.ANIMATOR_DURATION_SCALE,
      1f,
    ) == 0f
  }
}

val reduceMotion = rememberReduceMotion()
val spec = if (reduceMotion) snap() else tween<Float>(durationMillis = 300)

注意:优先使用 ValueAnimator.areAnimatorsEnabled()(API 26+)——当动画时长缩放为 0 时,它会返回 false。开发者选项中的“关闭动画”开关、“省电模式”以及面向用户的“移除动画”(设置 -> 辅助功能)开关都会将其设为 0。深入内容见 references/accessibility-mobile.md。


移动端手势(规范模式)

用户已经熟悉的五种手势。重用它们无需额外 UX 成本;重新发明只会增加阻力。

  • 边缘右滑返回: 在 iOS 上从屏幕起始边缘向内滑动,以弹出导航堆栈。绝不要覆盖此手势;在 Android 上通过预测性返回与之保持一致。该功能从 Android 13 (API 33) 起提供,需通过 android:enableOnBackInvokedCallback="true" 和 OnBackPressedCallback / PredictiveBackHandler 启用。在 Android 13-14 上,这些动画需通过开发者选项开启;从 Android 15 起,启用该功能的应用会自动显示系统返回主屏幕、跨 Activity 和跨任务动画。
  • 下拉刷新: 在滚动区域顶部向下拖动以重新获取数据。信息流、邮件和列表的标准手势。
  • 下拖关闭: 向下拖动模态表单和图片查看器,超过阈值(通常为 100-150pt)时将其关闭。
  • 双指缩放: 在图片、地图和可缩放画布上,用两根手指张开或捏合。遵守最小/最大缩放比例。
  • 列表项滑动操作: 在列表项上水平滑动,以显示上下文操作(删除、归档、标记为已读)。起始侧滑动和末尾侧滑动对应不同的操作集。

移动端性能预算

  • 冷启动: 在中端设备上低于 2s。基准设备:Android Pixel 4a、iPhone SE(第 2 代)。如果应用在 Pixel 4a 上耗时 4s,那么在用户实际拥有的低端设备上就会耗时 8s。
  • 帧预算: 60fps 时每帧 16.67ms,120fps 时每帧 8.33ms(ProMotion / 高刷新率 Android)。主线程上任何超过此时间的同步操作都会造成卡顿。
  • 二进制大小: 在添加大型媒体库之前,目标是 APK 小于 30MB、IPA 小于 50MB。Lottie/Rive 会增加 500KB 到 2MB。留意资源文件夹;通常罪魁祸首是未使用 WebP / 矢量格式的 PNG。
  • 电池: 后台不要持续占用 CPU。合并任务,使用平台调度器(Android 上的 WorkManager、iOS 上的 BGTaskScheduler);除非用户明确要求前台媒体播放,否则避免使用唤醒锁。
  • 网络: 遵循连接提示。Web:Save-Data 请求标头和 navigator.connection.saveData。iOS:URLSessionConfiguration.allowsCellularAccess 和 NWPathMonitor,用于区分蜂窝网络与 Wi-Fi。Android:使用 ConnectivityManager + NetworkCapabilities 检测按流量计费的网络。

反模式(错误 / 正确)

1. 仅通过悬停显示内容
/* BAD - on mobile the action button literally never appears */
.card .actions { opacity: 0; }
.card:hover .actions { opacity: 1; }
/* GOOD - visible by default, hover is a desktop enhancement */
.card .actions { opacity: 1; }

@media (hover: hover) and (pointer: fine) {
  .card .actions { opacity: 0; transition: opacity 0.15s ease-out; }
  .card:hover .actions { opacity: 1; }
}
2. 触控目标小于最小尺寸
// BAD - looks 32dp and reads as a 32dp target to whoever writes the next one.
// Material 3's IconButton actually applies minimumInteractiveComponentSize() itself, so
// the touch target stays 48dp here - but only because the component rescues you, and only
// while LocalMinimumInteractiveComponentEnforcement is on. On a custom Box or Row with the
// same modifier, the target really is 32dp and really is mistappable.
IconButton(
  onClick = onDelete,
  modifier = Modifier.size(32.dp),
) { Icon(Icons.Default.Delete, contentDescription = "Delete") }
// GOOD - 48dp minimum even when the visible icon is 24dp
IconButton(
  onClick = onDelete,
  modifier = Modifier.size(48.dp),
) {
  Icon(
    Icons.Default.Delete,
    contentDescription = "Delete",
    modifier = Modifier.size(24.dp),
  )
}
3. 忽略安全区域内边距
// BAD - the CTA sits under the home indicator on every modern iPhone
VStack {
  Spacer()
  Button("Continue", action: next)
    .frame(maxWidth: .infinity)
    .padding()
}
// GOOD - safeAreaInset keeps the button reachable and visible
ScrollView { content }
  .safeAreaInset(edge: .bottom) {
    Button("Continue", action: next)
      .frame(maxWidth: .infinity)
      .padding()
  }

快速参考:加载子技能

需求加载
手势深入解析references/gestures-deep.md
移动端无障碍深入解析references/accessibility-mobile.md
Compose 专用动画../compose-motion/SKILL.md
SwiftUI 专用动画../swiftui-motion/SKILL.md

来源

评论

支持 Markdown