mcpField
A
apple-design

apple-design

github.com/blockmatic设计与 UI1213 次安装4 周前更新
代码仓库

关于 apple-design

面向 Web 界面设计的技能,将 Apple WWDC 设计理念转化为 CSS、Pointer Events、requestAnimationFrame 和弹簧动画实践,重点关注即时响应、可中断手势与速度连续性。

主要功能

  1. 01即时且连续的交互反馈
  2. 02基于 Pointer 的直接操控
  3. 03可中断且保留速度的弹簧动画
  4. 04动量投射与吸附点
  5. 05空间关系一致的转场
  6. 06流畅帧动画的 Web 建议

适用场景

  1. 01设计可拖动的抽屉与面板
  2. 02构建带动量的轮播组件
  3. 03实现动画过程中的手势反转
  4. 04优化 Web 交互动效

安装

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

$npx skills add https://github.com/blockmatic/basilic-skills --skill apple-design

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

附带文件LICENSE.txt

详细介绍

Apple Design

初始响应

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

我已准备好帮助你在 Web 上构建流畅的 Apple 风格界面;我的知识来自 Apple WWDC 设计演讲,并已针对 Web 翻译。

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

Apple 如何构建界面,让它不再像一台电脑,而更像是你的延伸。这些知识来自 Apple 的 WWDC 设计演讲——主要是 Designing Fluid Interfaces(WWDC 2018)——并经过提炼,转换为 Web 平台的内容(CSS、Pointer Events、requestAnimationFrame、Motion/Framer Motion 等弹簧库)。

核心要点:当动效从当前屏幕值开始,继承用户的速度,将动量向前投射,并且可以在任意时刻被抓取和反向时,界面就会显得鲜活。 弹簧是实现这一切的工具,因为它们本质上可被中断,并能感知速度。

核心理念

“当我们让界面与思考和移动的方式保持一致时,奇妙的事情就会发生——它不再像一台电脑,而开始像是我们的无缝延伸。”

界面若表现得像物理世界,就会显得流畅:事物会即时响应、连续移动、承载动量、在边界处受到阻力,并且能在运动过程中改变方向。下面的一切都是为了更接近这一点。

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

1. 响应——消除延迟

一旦出现延迟,直接操控的感觉就会“断崖式下跌”。响应能力是一切的基础。

  • 在指针按下时响应,而不是等到松开。 按钮按下的瞬间就高亮显示。等到 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 轴速度不同时导致不同步。

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,则应收紧行距。
  • **将字重、字号和行距作为一个整体来构建层级,**而不是只调整字号。通过字重强调内容——既能增强视觉分量,又不占用更多空间。
  • 尊重用户的文本大小设置(动态字体)。让布局随文本缩放——使用 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