mcpField
A
apple-design

apple-design

github.com/davidhdev设计与 UI50.9k145 次安装1 天前更新
代码仓库

关于 apple-design

apple-design 是面向 Web 界面设计与开发的动画设计技能,将 Apple WWDC 的流体界面原则转化为 CSS 和指针交互实践,重点关注即时响应、可中断运动与动量传递。

主要功能

  1. 01指针交互与直接操控
  2. 02可中断的弹簧动画
  3. 03速度交接与动量投射
  4. 04空间一致性与锚定原点
  5. 05橡皮筋式边界反馈
  6. 06帧级流畅度建议

适用场景

  1. 01设计可拖拽并可随时反向的界面
  2. 02为抽屉和轮播选择基于动量的落点
  3. 03改进 Web 手势的跟手反馈

安装

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

$npx skills add https://github.com/davidhdev/react-bits --skill apple-design

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

详细介绍

Apple Design

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

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

核心理念

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

当一个界面的行为像物理世界时,它就是流畅的:物体即时响应、连续运动、携带动量、在边界处形成阻力,并且能在运动途中被改变方向。下文的一切都是为了更接近这一点。

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

1. 响应——消除延迟

一旦出现滞后,直接感就会“断崖式下跌”。响应是其他一切赖以建立的基础。

  • 在 pointer-down 时响应,而不是在释放时。 按钮被按下的瞬间就高亮它。等到 click/触摸抬起才显示反馈会显得毫无生气。
  • 对每一处延迟保持警惕。 审查防抖、人为计时器、过渡等待以及约 300ms 的点击延迟。输入路径上任何非必要的环节都是一种退步。
  • 反馈必须在交互过程中持续存在,而不仅仅在结束时。 对于拖拽、滑块或抽屉,全程让 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. 可中断性——最重要的单一原则

“思考与手势是并行发生的。”

每一个动画都必须能在任意时刻被中断和改变方向。用户必须能够在运动途中抓住一个正在移动的元素并将其反向,而无需等待动画结束。用户再次抓住一个正在关闭的模态框时,它应当跟随手指——而不是先完成关闭,然后再重新打开。

  • 绝不要在过渡期间锁定输入。
  • 始终从呈现(当前)值开始播放动画,而绝不要从目标值开始。 中断时,读取元素实时的屏幕变换,并从那里开始新的动画。从逻辑/目标值开始会导致可见的跳变。
  • 对于任何由手势驱动的东西,避免使用 CSS 过渡和 @keyframes——它们无法在运动途中被平滑地抓住并反向。弹簧默认从当前值开始动画,这正是中断所需要的。
  • 当手势反向时,要融合速度——不要硬切。 在反向时用一个动画替换另一个动画会造成速度不连续,形成一堵“砖墙”。那些在重新设定目标时延续速度的弹簧库可以避免这一点。(这正是 iOS 的附加动画在原生层面所做的;在 Web 上,请选择一个能从当前速度重新设定目标的弹簧库。)
  • 将 2D 运动分解为独立的 X 和 Y 弹簧。 当 X 和 Y 具有不同速度时,作用于 2D 距离的单一弹簧会失步。

4. 行为优先于动画——使用弹簧

"把动画想象成你和对象之间的对话,而不是由界面规定好的东西。"

预先编排、固定时长的动画无法响应新的输入。而弹簧可以——新的输入只是改变目标,运动依然保持连续。凡是用户可以触碰的东西,都应该优先考虑弹簧。

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

  • 阻尼比——控制过冲。1.0 = 临界阻尼,无回弹,平滑收敛。< 1.0 = 过冲并振荡。越低 = 越有弹性。
  • 响应——数值到达目标的速度,以秒为单位。越低 = 越干脆。这不是"时长"——弹簧没有固定时长;它的收敛时间由参数自然产生。

默认值:

  • 大多数 UI 从 阻尼 1.0(临界阻尼)开始——优雅而不引人分心。
  • 仅当手势本身带有动量时(轻扫、抛出、拖拽释放),才加入回弹(阻尼约 0.8)。一个刚刚淡入的菜单发生过冲会显得别扭;而一张你轻扫出去的卡片发生过冲则恰到好处。

Apple 实际采用的具体数值:

交互阻尼响应
移动 / 重新定位(例如画中画)1.00.4
旋转0.80.4
抽屉 / 面板0.80.3

Web 映射(Motion / Framer Motion): bounce + duration 的弹簧 API 与 Apple 的阻尼 + 响应高度对应。一个稳妥的团队风格是:默认在所有地方使用 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. 动量投影——动画到手势将要到达的位置

"用很小的输入,产生很大的输出。"

不要从释放点吸附到最近的边界。用速度来投影静止位置——就像滚动减速那样——然后吸附到离投影点最近的目标。这正是让一次轻扫感觉像是在把元素"抛"出去的原因。

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 实际采用的——请使用上面的指数衰减形式。这是优秀的底部面板和轮播(Vaul、Embla)中的标准行为。

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

"如果某个东西以一种方式消失,我们就期望它从它来的地方出现。"

  • 沿同一条路径进入和退出。 从右侧滑入的面板,必须向右侧退出。从右侧进入 / 从底部退出会让人感觉割裂、令人困惑。
  • 将交互锚定到其来源。 菜单、弹出框或面板应当从触发它的元素处出现——把 transform-origin 设为触发元素,这样按钮和内容之间的空间关系就一目了然。(这与弹出框从触发元素而非中心缩放的锚点意识是同一个要点。)
  • 在可逆过渡中镜像缓动,使去程路径与返回路径相匹配(为两个方向使用互逆的三次贝塞尔控制点)。

8. 在手势的方向上给出提示

人类会从运动轨迹预测最终状态。中间的运动应当预示事物将走向何处——控制中心模块"朝着你的手指生长并展开"。让中间帧指向结果,而不是盲目地插值到结果。

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. 手势设计细节("手感"检查清单)

  • 点按: 在触摸按下时高亮(即时),在触摸抬起时提交。在目标周围增加约 10px 的迟滞/命中内边距,并允许通过拖离再拖回来取消。
  • 拖拽/滑动: 在确定方向之前,要求一个小的移动阈值(迟滞,约 10px),然后 1:1 跟踪。
  • 从第一次移动起就并行检测所有可能的手势,然后在意图明确后果断取消落选者。避免只报告最终状态的识别器(swipeleft 类型的事件)——它们丢弃了你进行反馈所需的连续跟踪。
  • 尽量减少消歧延迟。 双击检测不可避免地会延迟单击;只在双击真正存在的地方付出这个代价。

11. 帧级流畅度

流畅度关乎帧里的内容,而不只是帧率。

  • 让每帧的位置变化低于感知阈值,以避免频闪。
  • 对于非常快速的运动,微妙的运动模糊/拉伸能编码速度,读起来比生硬的清晰拖影更好。
  • requestAnimationFrame 是 Web 上与显示同步的时钟(Apple 使用的是 CADisplayLink)。只对合成器友好的属性进行动画——transform 和 opacity——并在运动即将发生时用 will-change 给出提示。

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

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

  • 将导航/工具栏/面板构建为半透明层(backdrop-filter: blur() + 半透明背景),内容在其下方滚动——而不是占用固定条带的不透明栏。
  • 材质重量编码层级: 更深/更重的材质分隔结构区域(侧边栏);更轻的材质将注意力引向交互元素(按钮)。绝不要把一个浅色半透明表面叠在另一个之上——可读性会崩塌。
  • 更大的表面应当读起来更厚: 比小组件更强的模糊 + 更深的阴影。考虑上下文感知的阴影——在繁忙/文本内容之上更重以实现分隔,在素净背景之上更轻。
  • 变暗以聚焦,分隔以保持流畅。 模态任务将表面与变暗遮罩配对,并把背景向后/向下推。并行的非阻塞面板使用半透明和偏移,而不使用遮罩,这样流程不会被打断。对于堆叠的面板,逐层将每个父层变暗并向后推。
  • 活力感让文本在变化的背景上保持可读。 在模糊/半透明表面之上,不要使用平灰色文本——使用更高对比度、稍重的字重,以及小幅增加的字间距。把颜色放在实心层上,而不是半透明前景上。
  • 用滚动边缘效果,而不是生硬的分隔线。 不要在粘性头部下方用 1px 边框,而是在内容与浮动界面相交处淡入一个小型模糊/渐变遮罩——只在浮动 UI 真正与内容重叠的地方。
  • 实体化,而不只是淡入淡出。 对于玻璃/模糊表面,在进入/退出时同时动画模糊半径和缩放,使表面读起来像真实材质在到来,而不是单纯的透明度淡入淡出。
.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. 因果性 — 必须让人一眼看出反馈由何引发。在实际的因果事件上触发(开关翻转、条目归位吸附),并让反馈的特性与动作的物理质感相匹配。
  2. 和谐性 — 视觉、声音和触觉必须在同一帧触发。它们之间的延迟会破坏这种错觉。不要让 CSS 过渡拖慢音频/触觉(Vibration API)。
  3. 实用性 — 只在值得的地方添加反馈。把触觉/声音留给有意义的时刻(成功、错误、提交、吸附)。过度反馈会让用户习惯性地忽略所有反馈。

14. 减弱动效与无障碍

减弱动效并不意味着没有反馈 — 而是更柔和、不引起前庭不适的等效反馈。响应三种独立信号,并将其内建到你的组件中:

  • prefers-reduced-motion: reduce — 用简短的透明度交叉淡入淡出或静态过渡替代滑动/弹簧/视差。去掉弹性/过冲效果。保留有助于理解的透明度/颜色变化。
  • prefers-reduced-transparency: reduce — 让半透明表面更磨砂/更实:提高背景不透明度,去掉模糊。
  • prefers-contrast: more — 接近实色的背景,配以明确的对比边框。

另外:避免占满视口的动态背景、缓慢循环的振荡(接近 0.2 Hz / 每 5s 一个周期),以及突兀的亮度跳变(让深色↔浅色主题切换更平滑)。让大型移动物体在移动过程中保持半透明,并在大幅重定位时让大块表面淡出、就位后再淡入。

@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。)

  • 字距(letter-spacing)因尺寸而异 — 绝不要所有尺寸用同一个值。 大号展示文字需要负字距(字号越大,字母读起来越松散);小号文字需要略微正字距以提升可读性。固定的 letter-spacing 总会在某处出错。收紧标题,正文保持接近 0。
  • 行距(line-height)与尺寸反向关联。 大号标题紧一些,正文松一些。对于上行部/下行部较高的文字,要加大行距;对于密集、信息量大的 UI,则收紧行距。
  • 用字重 + 字号 + 行距作为一套来构建层级, 而不只是字号。用字重来强调 — 它能在不占用更多空间的情况下增加存在感。
  • 尊重用户的文字大小设置(Dynamic Type)。让布局随文字缩放 — 间距用 rem/em,而非固定 px — 这样更大的字体才不会破坏布局。
  • 优先使用平台系统字体, 而非自定义字体;它已经内置了光学尺寸、字距表和可读性调优。只有在有理由时才覆盖。
: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. 目的。 有意图地创作;决定不做什么。每项功能都会占用用户的时间、注意力并消耗信任——只在能带来回报的地方花费这份预算。
  2. 自主权。 让人们掌控一切:提供选择,不要强迫他们走单一路径。提供容错能力作为保障——误操作应易于撤销;只有面对真正具有破坏性且不可逆的操作时,才显示确认对话框(谨慎使用;过度使用会让人们习惯性地点过确认)。
  3. 责任。 以用户利益为先。隐私:在恰当时机提出请求,只索取所需信息,并说明清楚。安全:预判误用和伤害——尤其是 AI 场景(考虑过敏情况的食谱应用不得推荐有害食材)。添加预览、确认和免责声明;如果功能的风险超过其价值,就将其删去。
  4. 熟悉感。 以人们已有的认知为基础。使用既不过于直白、也不过于抽象的隐喻(垃圾桶表示删除),并遵循人们熟悉的使用规律。保持一致:外观相同的事物,行为必须相同,位置也应相同(在 macOS 上,关闭按钮始终位于左上角),这样人们才能预测接下来会发生什么。只有在能证明更好的情况下,才打破熟悉的模式——然后要进行测试,而不是想当然。
  5. 灵活性。 为不同的情境、设备和各种能力水平进行设计。适应平台(iPhone 适合快速触控;桌面端适合深度工作流和精确的指针控制),也适应具体情境。进行包容性设计(兼顾年龄、语言、专业水平和无障碍需求)。如果一种布局无法适合所有人,就让人们能够个性化设置——重新排列控件,隐藏不用的内容。
  6. 简洁——而非极简。 去除不必要的内容,让核心目的更加突出;把所有内容都塞在一个地方看似极简,却并不简洁。表达精炼(使用通俗语言、不用术语、减少步骤),表达清晰(利用层级——顺序、间距、对比度——让最重要的内容最显眼)。每个元素都必须有存在的理由;有时增加上下文反而能让设计更简洁(例如显示剩余时间的视频拖动条)。先展示常见操作路径,将高级选项放在下一层。
  7. 工艺。 对细节一丝不苟,才能建立信任。精美的排版、适应浅色/深色模式的颜色、清晰的图标,以及能提供即时、自然反馈的响应式动画。没有任何东西是随意的——每个间距、时间和对齐数值都是经过深思熟虑、能够解释的选择。滚动卡顿、图标未对齐,以及旋转设备时崩坏的布局,都会显得漫不经心。工艺需要不断迭代并经得起时间考验——随着功能和硬件变化,持续改进设计。
  8. 愉悦感。 这是做好其他七项原则后的结果,而不是额外撒上的亮片。确定希望人们感受到什么(平静、自信、兴奋),并在每项决策中强化这种感受。

以下实践规则有助于落实这些原则:

  • 反馈分为四类: 状态、完成、警告、错误。确认有意义的操作,展示持续进行的状态,在问题发生前发出警告,并在输入时即时验证(而不是等到提交时才验证)。
  • 路径指引。 每个屏幕都应回答:我在哪里?我能去哪里?那里有什么?我该如何离开?绝不要困住用户。
  • 分组与映射。 距离近意味着有关联;将控件放在其影响对象附近,并按照控件改变的内容来安排控件。如果需要用标签解释控件的作用,说明映射关系还不够清晰。
  • 具体明确的标签胜过稳妥但泛泛的标签。 根据导航项的内容为其命名(“进度”“资料库”),不要使用含糊的统称(“主页”)。具体明确才能让人们有所预期。

17. 流程

  • 通过交互原型进行探索——交互式演示胜过“百万张静态设计图”。 通过构建并试用来探索界面;可运行的原型也能设定明确的标准,避免最终实现平平无奇。
  • 将交互与视觉设计结合起来。 “你不应该能分辨两者的界限。” 动效不是像素完成后再添加的一层。
  • 在真实情境中与真实用户测试,并以全新的视角审视动效——用慢动作或逐帧播放,捕捉全速播放时看不见的细节。

快速参考

需求技术具体值
默认 UI 弹簧临界阻尼,无过冲damping 1.0,response 0.3–0.4
动量/轻扫弹簧欠阻尼,轻微回弹damping ~0.8,response 0.3–0.4
手势 → 弹簧速度传递释放速度若已归一化,则使用 gestureVelocity / (target − current)
轻扫落点投射动量current + (v/1000)·d/(1−d),d ≈ 0.998
平滑中断从呈现值(实时值)开始读取屏幕上的变换值
避免反向时出现“砖墙”重新设定目标时保留速度使用可混合速度的弹簧
可逆转场镜像缓动曲线反向 cubic-bézier
决定反向还是继续使用速度的方向,而非位置在释放时判断
1:1 拖动Pointer Events + 捕获保留抓取偏移量
反馈按下指针时持续提供绝不能只在结束时提供
边界使用橡皮筋效果,不要硬性停止逐步增加阻力
半透明界面元素backdrop-filter 图层内容在其下方滚动
字距根据字号设置,绝不固定收紧大号文本(-0.02em),正文接近 0
减少动态效果淡入淡出,不要滑动/弹簧@media (prefers-reduced-motion)

评论

支持 Markdown