返回技能市场
开发运维 安全

apple-design

@admin/apple-design

Apple's approach to interface design and fluid, physical motion, translated for the web. Use when building or reviewing gesture-driven UI, spring animations, drag/swipe/sheet interactions, momentum and interruptible transitions, translucent materials and depth, typography (optical sizing, tracking, leading), reduced-motion, or the design foundations (feedback, spatial consistency, restraint) behind Apple-style interfaces.

admin 热度 285v0.0.1

Apple 设计

初始响应

当首次调用此 skill 且没有提出具体问题时,只回复:

我已准备好帮助你在 Web 上构建流畅、Apple 风格的界面,我的知识来自 Apple 的 WWDC 设计演讲,并已为 Web 进行转译。

在用户提问之前,不得提供任何其他信息。

Apple 如何构建不再像计算机、而开始像你的延伸的界面。这些知识来自 Apple 的 WWDC 设计演讲——主要是 *Designing Fluid Interfaces*(WWDC 2018)——经过提炼并转译到 Web 平台(CSS、Pointer Events、requestAnimationFrame,以及 Motion/Framer Motion 等弹簧库)。

贯穿始终的主线是:当动效从当前屏幕上的值开始、继承用户的速度、将动量向前投射,并且可以在任意瞬间被抓住和反转时,界面就会显得有生命。 弹簧是让这一切变得自然的工具,因为它们天生可中断,并且感知速度。

核心思想

“当我们将界面对齐到我们思考和移动的方式时,某种神奇的事情会发生——它不再像一台计算机,而开始像我们无缝的延伸。”

当界面表现得像物理世界时,它就是流畅的:事物即时响应、连续移动、携带动量、在边界处抵抗,并且可以在运动中途被重定向。下面的一切都是接近这种状态的方法。

Apple 将设计框定为服务四种人类需求:安全/可预测性、理解、成就和愉悦。 这里的每条规则都服务于其中之一。

1. 响应——消灭延迟

一旦出现滞后,直接感就会“坠崖”。响应是其他一切建立的基础。

  • 在 pointer-down 时响应,而不是在释放时。 按钮被按下的瞬间就高亮它。等待 click/touch-up 才显示反馈,会感觉死气沉沉。
  • 警惕每一种延迟。 审查 debounce、人为定时器、过渡等待,以及约 300ms 的 tap 延迟。输入路径上任何并非必需的内容都是退步。
  • **反馈必须在交互*期间*连续,而不只是在结束时。** 对于拖拽、滑块或抽屉,全程让 UI 与指针 1:1 更新——绝不要在手势完成时才做动画。
/* Feedback lives on the press, and it's instant */
.button:active {
  transform: scale(0.97);
  transition: transform 100ms ease-out;
}

2. 直接操纵——1:1 跟踪

“触摸和内容应当一起移动。”

当用户拖动某物时,它必须紧贴手指——并尊重*从他们抓住的位置*产生的偏移。在抓住时吸附到元素中心,会立即打破错觉。

  • 使用 Pointer Events 与 setPointerCapture,这样即使指针离开元素边界,跟踪仍会继续。
  • 跟踪一段较短的速度/位置历史(最近几个 pointermove 事件),而不只是当前点——你在释放时需要速度。
el.addEventListener('pointerdown', (e) => {
  el.setPointerCapture(e.pointerId);
  const grabOffset = e.clientY - el.getBoundingClientRect().top; // respect where they grabbed
  // ...track position + timestamp history for velocity
});

3. 可中断性——最重要的单一原则

“想法和手势是并行发生的。”

每个动画都必须可以在任意时刻被中断和重定向。用户必须能够在元素运动途中抓住它并反转,而不必等待动画结束。用户再次抓住正在关闭的模态框时,它应该跟随手指——而不是先完成关闭再重新打开。

  • 在过渡期间绝不要锁定输入。
  • **始终从*呈现*(当前)值开始动画,而不是目标值。** 发生中断时,读取元素当前在屏幕上的 transform,并从那里开始新动画。从逻辑/目标值开始会导致可见跳跃。
  • 对于任何手势驱动的内容,避免 CSS transitions 和 @keyframes——它们无法在飞行途中被平滑抓住并反转。弹簧默认从当前值开始动画,这正是中断所需要的。
  • 当手势反转时,混合速度——不要硬切。 在反转时用另一个动画替换当前动画会造成速度不连续,即“砖墙”。能在重定向时携带速度的弹簧库可以避免这一点。(这是 iOS *additive animations* 原生做到的事;在 Web 上,选择一种能从当前速度重定向的弹簧库。)
  • 将 2D 运动分解为独立的 X 和 Y 弹簧。 当 X 和 Y 速度不同时,对 2D 距离使用单个弹簧会失去同步。

4. 用行为而非动画——使用弹簧

“把动画看作你和对象之间的对话,而不是界面规定好的东西。”

预先编排、固定时长的动画无法响应新输入。弹簧可以——新输入只是改变目标,运动保持连续。对于用户能触摸的任何东西,优先使用弹簧。

Apple 有意用两个对设计师友好的参数替换了物理三元组(质量/刚度/阻尼)。用这些来思考:

  • Damping ratio — 控制过冲。1.0 = 临界阻尼,无弹跳,平滑落定。< 1.0 = 过冲并振荡。越低 = 越弹。
  • Response — 值到达目标所需的时间,以秒计。越低 = 越敏捷。这不是“duration”——弹簧没有固定时长;它的落定时间由参数自然涌现。

默认值:

  • 大多数 UI 从 damping 1.0(临界阻尼)开始——优雅且不干扰。
  • 添加弹跳(damping ~0.8只有当手势本身携带动量时(flick、throw、drag release)。刚刚淡入的菜单有过冲会显得错误;你甩出的卡片有过冲则显得正确。

Apple 实际使用的具体值:

| 交互 | Damping | Response | | --- | --- | --- | | 移动 / 重定位(例如 PiP) | 1.0 | 0.4 | | 旋转 | 0.8 | 0.4 | | 抽屉 / 工作表 | 0.8 | 0.3 |

Web 映射(Motion / Framer Motion): bounce + duration 弹簧 API 与 Apple 的 damping + response 接近映射。安全的 house style 是默认到处使用 damping: 1.0 的弹簧;把弹跳保留给动量驱动、物理化的交互。

import { animate } from 'motion';

// Critically damped default (no overshoot)
animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 });

// Momentum interaction — a little bounce, only because a flick preceded it
animate(el, { y: target }, { type: 'spring', bounce: 0.2, duration: 0.4 });

5. 速度交接——拖拽与动画之间的接缝

当手势结束时,动画必须以手指的精确速度继续,这样拖拽和动画之间就没有可见接缝。这正是“流畅”与“尚可”之间最关键的细节。

将指针释放速度作为弹簧的初始速度传入。某些弹簧 API 需要相对速度——用到目标的剩余距离进行归一化:

relativeVelocity = gestureVelocity / (targetValue − currentValue)

示例:元素在 y=50,目标 y=150(还剩 100px),手指以 50px/s 移动 → 初始弹簧速度 = 50 / 100 = 0.5。Framer Motion / Motion 直接接受绝对 px/s 速度(velocity 选项),因此通常把原始值交给它。

6. 动量投射——动画到手势*将要*到达的位置

“输入小,输出大。”

不要从*释放点*吸附到最近边界。使用速度投射静止位置——就像滚动减速一样——然后吸附到最接近该投射点的目标。这正是让 flick 感觉像抛出元素的原因。

Apple 的精确投射函数(来自 *Designing Fluid Interfaces* 示例代码):

// decelerationRate ≈ 0.998 for normal scroll feel; 0.99 for snappier
function project(initialVelocity /* px/s */, decelerationRate = 0.998) {
  return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate);
}

const projectedEndpoint = currentPosition + project(releaseVelocity);
const target = nearestSnapPoint(projectedEndpoint);   // choose target from the projection
animateSpringTo(target, { velocity: releaseVelocity }); // then hand off velocity (§5)

注意:物理教科书中的 v²/(2·decel) *不是* Apple 实际使用的形式——使用上面的指数衰减形式。这是优秀 bottom-sheet 和 carousel(Vaul、Embla)中的标准行为。

7. 空间一致性——对称路径、锚定原点

“如果某个东西朝一个方向消失,我们期望它从它来的地方出现。”
  • 沿同一路径进入和退出。 从右侧滑入的面板必须向右退出。从右侧进入 / 从底部退出会感觉断开且令人困惑。
  • 将交互锚定到其来源。 菜单、popover 或 sheet 应当从触发它的元素产生——把 transform-origin 设置为触发器,这样按钮和内容之间的空间关系就很明显。(这与 popover 从触发器而非中心缩放的 origin-awareness 观点相同。)
  • 在可逆过渡上镜像缓动,使去程路径与回程路径匹配(对两个方向使用反向 cubic-bézier 控制点)。

8. 沿手势方向给出提示

人类会从轨迹预测最终状态。中间运动应当预示事物将去向何处——Control Center 模块会“向上并向外朝你的手指生长”。让中间帧指向结果,而不是盲目插值到结果。

9. 橡皮筋——软边界

在边缘处,应渐进抵抗而不是硬停。硬停会被读作“冻结”;连续阻力会被读作“有响应,但这里没有更多内容了”。应用阻尼,并且用户拖拽越靠近边界外,阻尼越大。

// The further past the bound, the less the element follows — real things slow before they stop
function rubberband(overshoot, dimension, constant = 0.55) {
  return (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
}

10. 手势设计细节(“手感”检查清单)

  • Tap: 在 touch-*down* 时高亮(即时),在 touch-*up* 时提交。在目标周围增加约 10px 的迟滞/命中 padding,并允许通过拖离和拖回取消。
  • Drag/swipe: 在确定方向前要求一个小的移动阈值(迟滞,约 10px),然后进行 1:1 跟踪。
  • 从第一次移动开始并行检测所有可能的手势,然后在意图明确时自信地取消落选者。避免只报告*最终*状态的识别器(swipeleft 类型事件)——它们会丢弃反馈所需的连续跟踪。
  • 最小化消歧延迟。 双击检测不可避免地延迟单击;只在确实存在双击的地方付出这种代价。

11. 帧级平滑

平滑关乎*帧中包含什么*,而不只是帧率。

  • 让每帧位置变化低于感知阈值,以避免频闪。
  • 对于非常快的运动,细微的 motion blur / stretch 能编码速度,并且比硬而锐利的条纹更好读。
  • requestAnimationFrame 是 Web 的显示同步时钟(Apple 使用 CADisplayLink)。只动画对合成器友好的属性——transformopacity——并在即将发生运动时用 will-change 提示。

12. 材质与深度——半透明传达层级

Apple 使用半透明材质作为浮动功能层,带来结构但不抢焦点。在 Web 上,可用 backdrop-filter 近似。

  • 将导航/工具栏/sheet 构建为半透明层backdrop-filter: blur() + 半透明背景),让内容在其下方滚动——不是占据固定条带的不透明栏。
  • 材质重量编码层级: 更深/更重的材质分隔结构区域(侧边栏);更浅的材质把注意力引向可交互元素(按钮)。绝不要把浅色半透明表面堆在另一个浅色半透明表面上——可读性会崩塌。
  • 更大的表面应读作更厚: 比小 chip 使用更强的模糊 + 更深的阴影。考虑 context-aware shadow——在复杂/文字内容上更重以获得分隔,在纯色背景上更轻。
  • 调暗以聚焦,分离以保持流程。 模态任务会把表面与调暗 scrim 配对,并把背景向后/向下推。平行、非阻塞面板使用半透明和偏移*而不带* scrim,以免打断流程。对于堆叠 sheet,逐层调暗并向后推每个父层。
  • Vibrancy 让文字在变化背景上保持可读。 在模糊/半透明表面上,不要使用扁平灰色文字——使用更高对比度、略重的字重,并增加一点 letter-spacing。把颜色放在不透明层上,而不是半透明前景上。
  • 使用滚动边缘效果,而不是硬分隔线。 不要在 sticky header 下方使用 1px 边框,而是在内容与浮动 chrome 相交处淡入一个小模糊/渐变遮罩——只在浮动 UI 确实重叠内容时。
  • 材质化,而不只是淡入。 对于玻璃/模糊表面,在进入/退出时一起动画模糊半径和缩放,使表面读作真实材质到达,而不是普通 opacity 淡入。
.toolbar {
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(20px) saturate(180%);
  border-top: 1px solid rgba(255, 255, 255, 0.4); /* bright top edge = light catching the material */
}

13. 多模态反馈——动效 + 声音 + 触感

组合感官的三条规则(来自 *Designing Audio-Haptic Experiences*):

  1. Causality — 必须明显知道是什么触发了反馈。在实际因果事件上触发它(toggle 翻转、item 吸附归位),并让它的特征匹配动作的物理性。
  2. Harmony — 视觉、声音和触感必须在同一帧触发。它们之间的延迟会摧毁错觉。不要让 CSS transition 落后于 audio/haptic(Vibration API)。
  3. Utility — 只在反馈值得其存在的地方添加反馈。把触感/声音保留给有意义的时刻(成功、错误、提交、snap)。过度反馈会训练用户忽略所有反馈。

14. 减少动态效果与无障碍

减少动态效果并不意味着*没有*反馈——而是意味着更温和、不引发前庭不适的等价反馈。响应三种独立信号,并把它们融入你的组件:

  • prefers-reduced-motion: reduce — 用短促 opacity cross-fades 或静态过渡替换 slides/springs/parallax。去掉 elastic/overshoot。保留有助于理解的 opacity/color 变化。
  • prefers-reduced-transparency: reduce — 让半透明表面更霜化/更不透明:提高背景不透明度,去掉模糊。
  • prefers-contrast: more — 接近不透明的背景,并带有明确、对比色的边框。

另外:避免全视口移动背景、缓慢循环振荡(接近 0.2 Hz / 每 5 秒一个周期),以及突兀亮度跳变(让 dark↔light 主题切换缓动)。让大型移动物体在移动时半透明,并在大范围重定位期间让大表面淡出、落定后再淡入。

@media (prefers-reduced-motion: reduce) {
  .sheet { transition: opacity 200ms ease; transform: none !important; }
}
@media (prefers-reduced-transparency: reduce) {
  .toolbar { background: white; backdrop-filter: none; }
}

15. 排版——光学尺寸、字距、行距

Apple 设计字体时会随尺寸改变形状;同样的纪律也适用于 Web。(来自 *The Details of UI Typography*,WWDC 2020。)

  • Tracking(letter-spacing)是尺寸特定的——绝不能对所有尺寸使用一个值。 大号 display 文本需要*负* tracking(字母变大时读起来太远);小文本需要略微*正* tracking 以提高可读性。固定的 letter-spacing 在某个尺寸上是错误的。收紧标题,让正文接近 0
  • Leading(line-height)与尺寸反向相关。 大标题收紧,正文放松。对于上升部/下降部较高的脚本增加行高;对于密集、信息量大的 UI 收紧行高。
  • 把层级作为一组由 weight + size + leading 构建, 而不只是 size。用 weight 强调——它增加存在感而不占用更多空间。
  • 尊重用户的文本大小设置(Dynamic Type)。让布局*随*文本缩放——使用 rem/em 的 spacing,而不是固定 px——这样更大字体不会破坏布局。
  • 默认使用平台的 system font,再考虑自定义字体;它已经自带 optical sizing、tracking tables 和可读性调优。只有在有理由时才覆盖。
:root { font: 100%/1.5 system-ui, sans-serif; } /* body: system font, comfortable leading */

.display {
  font-size: clamp(2rem, 5vw, 4rem);
  line-height: 1.05;        /* tight leading for large text */
  letter-spacing: -0.02em;  /* negative tracking as it grows */
  font-optical-sizing: auto;
}

16. 设计基础——八项原则

上面的动效与工艺服务于 Apple 的八项设计原则(*Principles of Great Design*,WWDC 2026)。把这些原则作为推理时使用的名字:

  1. Purpose。 带着意图创造;决定*不*构建什么。每个功能都在索取用户的时间、注意力和信任——只把这份预算花在有回报的地方。
  2. Agency。 让人保持控制:提供选择,不要强制单一路径。用宽恕来支持它——为失误提供容易的 undo,只在真正破坏性且不可逆的操作中使用确认对话框(谨慎使用;过度使用会训练人们一路点击通过)。
  3. Responsibility。 为用户利益行动。隐私:在正确时刻询问,只询问所需内容,并透明。安全:预料误用和伤害——尤其是在 AI 中(具备过敏意识的食谱应用不得建议有害成分)。添加 previews、confirmations、disclaimers;如果风险超过价值,就砍掉功能。
  4. Familiarity。 建立在人们已经知道的东西上。使用既不过于字面也不过于抽象的隐喻(trash can 表示 delete),并尊重它们的物理性。保持一致:看起来相同的东西必须行为相同,并位于相同位置(close 在 macOS 上始终在左上角),这样人们才能预测接下来会发生什么。只有当你能证明它更好时,才打破熟悉模式——然后测试它,不要假设。
  5. Flexibility。 为不同上下文、设备和完整能力范围进行设计。适应平台(iPhone = 快速 touch;desktop = 深度工作流与精确 pointer control)并适应情境。包容性设计(年龄、语言、专业程度、无障碍)。当没有单一布局适合所有人时,让人们个性化——重新排列控件,隐藏他们不使用的东西。
  6. Simplicity——not minimalism。 去掉不必要的东西,让核心目的发光;把所有东西塞在一个地方看起来 minimal 但不 simple。要简洁(plain language,无术语,更少步骤)并清晰(使用层级——顺序、spacing、contrast——让最重要的东西最明显)。每个元素都必须赢得其位置;有时*添加* context 会简化(显示剩余时间的 video scrubber)。先展示常用路径,把高级选项放在更深一层。
  7. Craft。 对细节的不妥协关注建立信任。Beautiful typography、适应 light/dark 的颜色、清晰 iconography,以及给出即时、自然反馈的 responsive animations。没有什么是随机的——每个 spacing、timing 和 alignment 值都是你可以辩护的刻意选择。抖动的 scroll、未对齐的 icons,以及在旋转时崩溃的 layouts 会被读作粗心。Craft 需要迭代和长期演进——随着功能和硬件变化持续演化设计。
  8. Delight。 把其他七项做对的结果,而不是在顶部加上 confetti。决定你希望人们感受的情绪(calm、confident、excited),并在每个决策中强化它。

服务于这些原则的战术规则:

  • Feedback 有四种: status、completion、warning、error。确认有意义的动作,暴露进行中的状态,在问题发生前警告,并 inline 验证(不是在提交时)。
  • Wayfinding。 每个屏幕都应回答:我在哪里?我能去哪里?那里有什么?我如何出去?绝不要困住用户。
  • Grouping & mapping。 邻近性暗示关系;把控件放在它影响的东西附近,并排列控件以镜像它们改变的内容。如果你需要 label 来解释控件,mapping 就很弱。
  • 直接、具体的 labels 胜过安全、泛化的 labels。 用内容命名导航项(“Progress”、“Library”),而不是模糊的伞状词(“Home”)。具体性创造可预测性。

17. 流程

  • 交互式原型——一个交互式 demo 值得“一百万个静态设计”。 你通过构建和玩它来发现界面;可工作的原型也设定了具体标准,防止平庸的最终实现。
  • 一起设计交互与视觉。 “你不应该能分辨出一个在哪里结束、另一个在哪里开始。” Motion 不是像素之后添加的一层。
  • 在真实上下文中与真实用户测试,并用新鲜眼光审查动效——慢动作 / 逐帧播放,以发现全速下不可见的问题。

快速参考

| 需求 | 技术 | 具体值 | | --- | --- | --- | | 默认 UI 弹簧 | 临界阻尼,无过冲 | damping 1.0, response 0.3–0.4 | | 动量 / flick 弹簧 | 欠阻尼,轻微弹跳 | damping ~0.8, response 0.3–0.4 | | 手势 → 弹簧速度 | 交接释放速度 | 若归一化则为 gestureVelocity / (target − current) | | flick 落点 | 投射动量 | current + (v/1000)·d/(1−d)d ≈ 0.998 | | 干净中断 | 从呈现(实时)值开始 | 读取屏幕上的 transform | | 避免反转“砖墙” | 通过重定向携带速度 | 能混合速度的弹簧 | | 可逆过渡 | 镜像缓动曲线 | 反向 cubic-bézier | | 决定反转还是提交 | 使用速度符号,而不是位置 | 在释放时 | | 1:1 拖拽 | Pointer Events + capture | 尊重抓取偏移 | | 反馈 | 在 pointer-down 时,连续 | 绝不只是在结束时 | | 边界 | 橡皮筋,不要硬停 | 渐进抵抗 | | 半透明 chrome | backdrop-filter 层 | 内容在其下方滚动 | | 字体 tracking | 尺寸特定,绝不固定 | 收紧大字(-0.02em),正文接近 0 | | 减少动态效果 | cross-fade,而不是 slide/spring | @media (prefers-reduced-motion) |

qianwen skills install @admin/apple-design