关于 motion-system
motion-system 是面向 iOS 团队的技能,用于审查应用的动画与触觉反馈,并提出贴合应用的精简动效系统。它要求先获批准再修改调用位置,并指导实现和编译检查。
主要功能
- 01审查动画、计时、触觉反馈和减少动态效果
- 02按交互用途归类动效
- 03在 60fps 库中查找参考
- 04先提出命名参数,再改代码
- 05实现并类型检查 Swift 动效文件
适用场景
- 01统一 iOS 应用中不一致的动画时长
- 02为功能定义动效和触觉反馈角色
- 03在迁移调用位置前准备动效提案
MCP 相关
该技能使用 60fps MCP 服务搜索动效示例并获取动效代码。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ 60fps-design/ plugin --skill motion-system安装后在 Claude Code 中输入/motion-system确认已加载。
详细介绍
动效系统
当同一类时刻总以相同方式动起来时,应用才会让人感觉浑然一体。大多数代码库都会逐渐偏离:十几种时长、彼此难以区分的弹簧动画,以及被用于所有场景的成功触感反馈。你的任务是用少数几个命名值取代这种偏离,这些值应以应用自身最佳动效和 60fps 库中真实应用的动效为依据。
交付内容是一份提案,然后是一个新文件。在用户同意提案之前,不要修改现有调用点。
1. 确定最低支持版本
读取应用目标的 IPHONEOS_DEPLOYMENT_TARGET。如果目标设置了自己的值,它会覆盖项目级的值,无论高低。检查项目定义该值的位置:.xcodeproj、XcodeGen project.yml、.xcconfig 文件或 Package.swift。你提议的每个 API 都必须在该版本中可用。同时记下 Swift 语言模式和任何默认 Actor 隔离设置;第 5 步编译时会用到这些信息。
2. 梳理应用当前的动效
搜索代码中的所有动效值,以及所有与这些值同步计时的内容:
- 动画:
withAnimation(、.animation(、.spring(、.interactiveSpring、.interpolatingSpring、.smooth、.snappy、.bouncy、.easeIn、.easeOut、.easeInOut、.linear、.default、duration:、.delay(、response:、dampingFraction:。单独出现的.smooth、.snappy或.bouncy也是一个值(默认时长为 0.5 秒),应计入。 - 转场和效果:
.transition(、contentTransition、matchedGeometryEffect、matchedTransitionSource、phaseAnimator、keyframeAnimator、symbolEffect。 - 与动画耦合的计时:
Task.sleep、asyncAfter、完成处理程序。这些最不适合随意更改:如果某个休眠时长是按 0.35 秒的动画调好的,动画一变就会出问题。 - 触感反馈:
sensoryFeedback、UI...FeedbackGenerator类,以及应用对它们的任何封装的调用点(有 50 个调用点的HapticManager比类本身更重要)。 - 减少动态效果:读取它的各种方式(
accessibilityReduceMotion、isReduceMotionEnabled)。 - 现有辅助工具:任何
Motion、Animation或触感反馈扩展或管理器。
按用途而不是按文件对值分组:
- 反馈:按压、切换、选择、原地小幅替换。应该迅速。
- 变化:内容替换、行出现、数字变化。
- 入场:工作表、卡片或屏幕元素进入。
- 强调:少见但应让人有所感受的时刻(成功、里程碑)。
- 触感反馈:不同类型的触感反馈分别用于什么场景。
统计每组中不同值的数量。这个数量就是问题陈述:“入场效果使用了 8 种不同的时长,范围为 0.3 秒至 0.7 秒”。有些动效不属于任何用途,不应纳入系统:手势跟踪(视差、跟随手指移动的拖动)以及注释中说明不可更改的值。将这些列为有意保留的例外。
在现有基础上扩展。如果应用已有触感反馈封装(通常带有应用内开关),新增触感反馈用途必须通过它实现,不能绕过它:sensoryFeedback 会绕过该开关。如果现有动效辅助工具有缺陷或已停用,应说明并提议修复或移除它,而不是另起一个辅助工具。
3. 找出应用的基调
阅读现有动效和设计(颜色、字体、文案风格是否活泼),用几个词概括基调:沉静而精准、柔和而亲切、鲜明而活泼。你提议的每个值都必须符合这一基调。金融应用不应使用游戏风格的弹簧动画。
4. 以真实应用为依据
每个需要参考案例的任务都调用一次 60fps_search_shots。描述动作和氛围,而不是物体:“屏幕加载时,各个区块依次淡入,氛围平静”能找到元素出现的效果,而“卡片出现”则会找到卡片堆叠效果。挑选 3 到 5 个氛围相符的片段,根据标题、摘要、intensity 和 mood 判断,而不是看分数。优先选择 motion 标签暗示弹簧、淡入淡出或交叉淡化的片段:由 TimelineView 或自定义计算驱动的时间安排没有可读取的动画值。如果找不到符合氛围的片段(平静的应用很少能找到平静的庆祝效果),那么应用自身的最佳效果就是参考,并说明这一点。
只对这些片段调用 60fps_get_motion_code。仅当 source 为 generated 时才使用它,并阅读 generated 字段中的具体行:withAnimation 和 .animation 的值、弹簧参数、延迟、触觉调用。忽略响应顶层的 animation、recipes 和 stagger_delay 字段:它们是通用的起始值,在不同片段间通常完全相同。recipes 结果完全没有片段编号,计算时间安排的演示也没有,因此跳过它,另选一个。该文件是可自行运行的演示,因此跳过其中的循环和重播。
在此技能中,即使工具自身的说明要求展示代码文件,也不要向用户展示:你只需要每个文件中的几行时间参数,而文件很长。这些数值来自 AI 对真实应用视频的复现,因此是可靠的参考,而非测量结果。每次调用都会计入用户的每日限额,因此最多调用 5 次。
5. 提出系统方案
保持精简。没人记得住的系统就不算系统。
- 动画:大约 4 个命名值,每个值对应一种用途,例如
Motion.feedback、Motion.change、Motion.arrival、Motion.emphasis,以及步骤 2 中有意保留的例外情况。 使用系统最低版本允许的最新弹簧 API。在 iOS 17 及更高版本中,优先使用.spring(duration:bounce:),或带duration:的.smooth、.snappy和.bouncy。请注意,extraBounce:会叠加到预设自身的弹性效果上(.snappy已有一定弹性),因此请使用.spring(duration:bounce:)编写转换后的值。要转换.spring(response:dampingFraction:):duration 等于 response;damping fraction 小于或等于 1 时,bounce 等于 1 减去 damping fraction;大于 1 时,bounce 等于 1 除以 damping fraction,再减去 1。丢弃任何blendDuration。 - 交错延迟:对于按顺序显示内容的列表,每个条目设置一个延迟步长。只对前几个条目(约 8 个)设置交错延迟, 其余条目同时显示,这样长列表不会让用户等待,而且步长也足够大,便于察觉。
- 触觉反馈:为选择、轻触、主要操作或破坏性操作的强触、成功和错误各设一个角色,但只设置应用中已有调用点 或方案中新增调用点的角色。成功反馈只用于真正的成就,绝不用作常规刷新或同步的反馈。每个事件只触发一次触觉反馈,列表中的每个条目都不要分别触发。
- 减少动态效果:为整个应用制定一条规则。在视图中读取
@Environment(\.accessibilityReduceMotion);在模型及其他没有环境值的代码中,在主线程上读取UIAccessibility.isReduceMotionEnabled。过渡效果改为.opacity。仅替换动画并不能停止移动:由状态驱动的scaleEffect或offset仍会缩放或移动,只是速度更快,因此应将这些值保持在静止状态。布局变化(插入行、列表变长) 总会带动相邻内容移动;这种情况下,实用的规则是使用较短的.easeInOut(约 0.2 s)。 有意义的变化仍应发生,只是不要产生位移。返回nil的辅助函数会让变化立即发生,而不是淡入淡出:只有在有意选择立即变化时 (例如整个屏幕移动)才使用nil;规则要求交叉淡化时,则使用较短的.easeInOut。
用表格呈现:名称、值、用途、将替换哪些现有值,以及值的来源(截图或应用自身代码)。然后按文件分组, 列出所有将要变更的调用点,并注明旧值和新值,以及所有需要一并调整的 sleep。修改前先征求同意。
6. 编写文件
用户同意后,将这些值写入应用现有的 motion 文件或类型(如果步骤 2 中发现了此类文件或类型);否则新建
Motion.swift,并用简短注释说明每个值的用途、触觉反馈角色(通过应用现有的触觉反馈封装;如果没有,则使用 sensoryFeedback),
以及用于应用减少动态效果规则的简短辅助函数。
交付前先编译:尽可能构建应用;否则,根据步骤 1 中确定的最低版本对该文件进行类型检查,并为其使用的任何应用类型添加一个小型存根文件:
xcrun --sdk iphonesimulator swiftc -typecheck -target arm64-apple-ios<floor>-simulator \
-swift-version <mode> Motion.swift Stub.swift
如果项目设置了隔离和并发标志,也要添加。-target 的最低版本会捕获使用过新 API 的情况。之后分批迁移调用点,逐个文件处理,
仅在用户要求时进行,并将每个相关的 sleep 调整为动画完成时触发。
用平实的语言写作:句子简短,用常见词语。绝不要使用 em dash 或 en dash。
不适用范围
不适用于 Android、Flutter、React Native 或 Web。若只有一个动画,请使用 motion-reference 技能。若要判断应用是否应该添加动画,请先使用 find-animation-opportunities。