apple-design

apple-design

热门

Apple 的界面设计与流畅物理动效方法,转化为 Web 实现。适用于构建或审查手势驱动的 UI、弹簧动画、拖拽/滑动/面板交互、动量和可中断过渡、半透明材质与深度、排版(光学尺寸、字距、行距)、减少动效,或 Apple 风格界面背后的设计基础(反馈、空间一致性、克制)。

1.1万Star
622Fork
更新于 2026/7/11
SKILL.md
readonly只读
name
apple-design
description

Apple 的界面设计与流畅物理动效方法,转化为 Web 实现。适用于构建或审查手势驱动的 UI、弹簧动画、拖拽/滑动/面板交互、动量和可中断过渡、半透明材质与深度、排版(光学尺寸、字距、行距)、减少动效,或 Apple 风格界面背后的设计基础(反馈、空间一致性、克制)。

Apple Design

Apple 如何构建那些不再像电脑、而像你身体延伸的界面。这些知识来自 Apple 的 WWDC 设计讲座——主要是《设计流畅界面》(WWDC 2018)——提炼并转化为 Web 平台(CSS、Pointer Events、requestAnimationFrame、Motion/Framer Motion 等弹簧库)。

贯穿始终的主线是:当动效从屏幕当前值开始、继承用户速度、向前投射动量、并且可以随时抓取和反转时,界面就有了生命力。 弹簧是实现这一切自然的工具,因为它们天生可中断且感知速度。

核心理念

"当我们将界面与思维和运动方式对齐时,神奇的事情发生了——它不再像电脑,而开始像我们无缝的延伸。"

流畅的界面行为如同物理世界:事物即时响应、连续运动、携带动量、在边界处抵抗,并且可以在运动中途被重新定向。以下所有内容都是为了更接近这一目标。

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

1. 响应——消除延迟

一旦出现滞后,直接感就会"断崖式下跌"。响应是所有其他内容的基础。

  • 在指针按下时响应,而不是释放时。 按钮被按下的瞬间就高亮。等待 click/触摸抬起才显示反馈会显得死板。
  • 警惕每一次延迟。 审查防抖、人为定时器、过渡等待以及约 300ms 的点击延迟。输入路径上任何非必要的内容都是退化。
  • 反馈必须在交互过程中持续,而不仅仅在结束时。 对于拖拽、滑块或抽屉,在整个过程中让 UI 与指针 1:1 更新——绝不要只在手势完成时才动画。
/* 反馈存在于按下瞬间,且是即时的 */
.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; // 尊重抓取位置
  // ...记录位置和时间戳历史用于速度
});

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

"思考和手势是并行发生的。"

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

  • 绝不要在过渡期间锁定输入。
  • 始终从呈现(当前)值开始动画,而不是目标值。 中断时,读取元素在屏幕上的实时 transform,然后从那里开始新动画。从逻辑/目标值开始会导致可见的跳跃。
  • 避免在手势驱动的内容中使用 CSS 过渡和 @keyframes——它们无法在运动中途被平滑抓取和反转。弹簧默认从当前值开始动画,这正是中断所需要的。
  • 当手势反转时,混合速度——不要硬切。 在反转点用一个动画替换另一个会造成速度不连续,形成"砖墙"。那些在重新定位时携带速度的弹簧库可以避免这一点。(这就是 iOS 的叠加动画原生实现的方式;在 Web 上,选择一个能从当前速度重新定位的弹簧库。)
  • 将二维运动分解为独立的 X 和 Y 弹簧。 在二维距离上使用单个弹簧会在 X 和 Y 速度不同时导致不同步。

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

"把动画看作你和对象之间的对话,而不是界面预设的东西。"

一个预先编写、固定时长的动画无法响应新输入。而弹簧可以——新输入只是改变目标,运动保持连续。对于任何用户可以触摸的内容,都使用弹簧。

Apple 有意将物理三元组(质量/刚度/阻尼)替换为两个设计师友好的参数。请这样思考:

  • 阻尼比——控制过冲。1.0 = 临界阻尼,无弹跳,平滑稳定。< 1.0 = 过冲并振荡。数值越低越有弹性。
  • 响应时间——值到达目标的速度,以秒为单位。数值越低越灵敏。这不是"持续时间"——弹簧没有固定时长;其稳定时间由参数决定。

默认值:

  • 大多数 UI 从 阻尼 1.0(临界阻尼)开始——优雅且不分散注意力。
  • 仅当手势本身携带动量(轻弹、投掷、拖拽释放)时添加弹跳(阻尼约 0.8)。一个刚刚淡入的菜单过冲会感觉不对;而你轻弹的卡片过冲则感觉正确。

Apple 实际使用的具体值:

交互 阻尼 响应时间
移动/重新定位(例如画中画) 1.0 0.4
旋转 0.8 0.4
抽屉/面板 0.8 0.3

Web 映射(Motion / Framer Motion): bounce + duration 弹簧 API 与 Apple 的阻尼 + 响应时间紧密对应。一个安全的内部风格是默认在所有地方使用 damping: 1.0 弹簧;将弹跳保留给动量驱动的物理交互。

import { animate } from 'motion';

// 临界阻尼默认(无过冲)
animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 });

// 动量交互——轻微弹跳,仅因为之前有轻弹
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 的精确投射函数(来自《设计流畅界面》示例代码):

// decelerationRate ≈ 0.998 用于正常滚动感觉;0.99 用于更灵敏
function project(initialVelocity /* px/s */, decelerationRate = 0.998) {
  return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate);
}

const projectedEndpoint = currentPosition + project(releaseVelocity);
const target = nearestSnapPoint(projectedEndpoint);   // 从投射中选择目标
animateSpringTo(target, { velocity: releaseVelocity }); // 然后交接速度(§5)

注意:物理教科书中的 v²/(2·decel) 不是 Apple 实际使用的——请使用上述指数衰减形式。这是优秀底部面板和轮播(Vaul、Embla)中的标准行为。

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

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

  • 沿相同路径进入和退出。 从右侧滑入的面板必须从右侧关闭。从右侧进入/从底部退出会让人感到脱节和困惑。
  • 将交互锚定到其来源。 菜单、弹出框或面板应从触发它的元素处开始——将 transform-origin 设置为触发器,这样按钮和内容之间的空间关系就一目了然。(这与弹出框从其触发器缩放而非中心缩放的原点感知相同。)
  • 在可逆过渡上镜像缓动,使退出路径与返回路径匹配(对两个方向使用反相的 cubic-bézier 控制点)。

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

人类通过轨迹预测最终状态。中间运动应预示事物的发展方向——控制中心模块"向上向外朝着你的手指生长"。让中间帧指向结果,而不仅仅是盲目地插值到结果。

9. 橡皮筋效果——软边界

在边界处,渐进抵抗而不是硬性停止。硬性停止读作"冻结";持续抵抗读作"响应,但这里没有更多内容了"。应用阻尼,随着用户拖拽超出边界的距离增加而增大。

// 超出边界越远,元素跟随越少——真实物体在停止前会减速
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)。仅动画合成器友好的属性——transformopacity——并在即将发生运动时使用 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); /* 亮色顶部边缘 = 光线捕捉材质 */
}

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

组合感官的三条规则(来自《设计音频-触觉体验》):

  1. 因果性——必须清楚是什么导致了反馈。在实际因果事件(开关翻转、项目吸附到位)上触发它,并使其特征与动作的物理性匹配。
  2. 和谐性——视觉、声音和触觉必须在同一帧上触发。它们之间的延迟会破坏幻觉。不要让 CSS 过渡滞后于音频/触觉(Vibration API)。
  3. 实用性——只在值得的地方添加反馈。将触觉/声音保留给有意义的时刻(成功、错误、提交、吸附)。过度反馈会训练用户忽略所有反馈。

14. 减少动效与无障碍

减少动效并不意味着没有反馈——而是更温和、非前庭的等效方式。响应三个独立信号并将其融入你的组件:

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

此外:避免全视口移动背景、慢速循环振荡(接近 0.2 Hz / 每 5 秒一个周期)以及突然的亮度跳跃(平滑暗↔亮主题变化)。使大型移动物体在移动时半透明,并在大型重新定位时淡出大表面,稳定后再淡入。

@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。(来自《UI 排版的细节》,WWDC 2020。)

  • 字距(letter-spacing)是尺寸相关的——绝不要对所有尺寸使用一个值。 大号展示文本需要字距(字母随着变大看起来间距过大);小号文本需要轻微正字距以提高可读性。固定的 letter-spacing 在某些地方是错误的。收紧标题,正文保持在 0 附近。
  • 行距(line-height)与尺寸成反比。 大标题收紧,正文放松。对于有高上升/下降笔画的文字增加行距;对于密集、信息量大的 UI 收紧行距。
  • 通过字重 + 尺寸 + 行距作为一组来构建层级,而不仅仅是尺寸。用字重强调——它增加存在感而不占用更多空间。
  • 尊重用户的文本大小设置(Dynamic Type)。布局随文本缩放——间距使用 rem/em,而不是固定 px——这样更大的字体不会破坏布局。
  • 在自定义字体之前默认使用平台系统字体;它已经内置了光学尺寸、字距表和可读性调整。只有在有理由时才覆盖。
:root { font: 100%/1.5 system-ui, sans-serif; } /* 正文:系统字体,舒适行距 */

.display {
  font-size: clamp(2rem, 5vw, 4rem);
  line-height: 1.05;        /* 大文本收紧行距 */
  letter-spacing: -0.02em;  /* 随着变大使用负字距 */
  font-optical-sizing: auto;
}

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

上述动效和工艺服务于 Apple 的八项设计原则(《伟大设计的原则》,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
干净中断 从呈现(实时)值开始 读取屏幕上的 transform
避免反转"砖墙" 通过重新定位携带速度 混合速度的弹簧
可逆过渡 镜像缓动曲线 反相 cubic-bézier
决定反转还是提交 使用速度符号,而不是位置 在释放时
1:1 拖拽 Pointer Events + capture 尊重抓取偏移
反馈 在指针按下时,持续 绝不仅结束时
边界 橡皮筋,不要硬停 渐进抵抗
半透明镀铬 backdrop-filter 内容在其下方滚动
字体字距 尺寸相关,从不固定 收紧大文本(-0.02em),正文接近 0
减少动效 交叉淡入淡出,而不是滑动/弹簧 @media (prefers-reduced-motion)