apple-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseApple Design
Apple设计
How Apple builds interfaces that stop feeling like a computer and start feeling like an extension of you. This knowledge comes from Apple's WWDC design talks — chiefly Designing Fluid Interfaces (WWDC 2018) — distilled and translated into the web platform (CSS, Pointer Events, , spring libraries like Motion/Framer Motion).
requestAnimationFrameThe through-line: an interface feels alive when motion starts from the current on-screen value, inherits the user's velocity, projects momentum forward, and can be grabbed and reversed at any instant. Springs are the tool that makes all of this natural, because they are inherently interruptible and velocity-aware.
Apple如何打造不再像计算机、而是成为用户延伸的界面。这些知识源自Apple的WWDC设计讲座——主要是《设计流畅界面》(WWDC 2018)——经过提炼并适配Web平台(CSS、Pointer Events、,以及Motion/Framer Motion等弹簧动画库)。
requestAnimationFrame核心主线:**当动效从屏幕当前值启动、继承用户操作速度、传递动量前进,且可随时被抓取并反向时,界面会给人鲜活的感觉。**弹簧动画是实现这一切的理想工具,因为它天生支持中断且能感知速度。
The Core Idea
核心理念
"When we align the interface to the way we think and move, something magical happens — it stops feeling like a computer and starts feeling like a seamless extension of us."
An interface is fluid when it behaves like the physical world: things respond instantly, move continuously, carry momentum, resist at boundaries, and can be redirected mid-motion. Everything below is a way to get closer to that.
Apple frames design as serving four human needs: safety/predictability, understanding, achievement, and joy. Every rule here serves one of them.
“当我们让界面贴合人类的思考与行动方式时,神奇的事情就会发生——它不再像计算机,而是成为我们无缝的延伸。”
当界面表现得如同物理世界时,它就是流畅的:事物即时响应、连续移动、携带动量、在边界处产生阻力,且可在运动中途改变方向。以下所有内容都是为了更接近这一目标。
Apple将设计定义为满足人类四大需求:安全/可预测性、可理解性、成就感、愉悦感。这里的每一条规则都服务于其中一项需求。
1. Response — kill latency
1. 响应性——消除延迟
The moment lag appears, the feeling of directness "falls off a cliff." Response is the foundation everything else is built on.
- Respond on pointer-down, not on release. Highlight a button the instant it's pressed. Waiting for /touch-up to show feedback feels dead.
click - Be vigilant about every latency. Audit debounces, artificial timers, transition waits, and the ~300ms tap delay. Anything on the input path that isn't essential is a regression.
- Feedback must be continuous during the interaction, not just at the end. For a drag, slider, or drawer, update the UI 1:1 with the pointer the whole way through — never animate only when the gesture completes.
css
/* Feedback lives on the press, and it's instant */
.button:active {
transform: scale(0.97);
transition: transform 100ms ease-out;
}一旦出现延迟,直接操作的手感就会“断崖式下跌”。响应性是构建一切的基础。
- 在指针按下时响应,而非松开时。按钮被按下的瞬间就高亮显示。等待/触摸抬起再展示反馈会显得生硬。
click - 警惕每一处延迟。检查防抖机制、人工定时器、过渡等待,以及约300ms的点击延迟。输入路径中任何非必要的环节都是倒退。
- 反馈必须在交互过程中持续提供,而非仅在结束时。对于拖拽、滑块或抽屉组件,全程让UI与指针1:1同步更新——绝不要只在手势完成时才执行动画。
css
/* 反馈在按下时触发,且即时生效 */
.button:active {
transform: scale(0.97);
transition: transform 100ms ease-out;
}2. Direct manipulation — 1:1 tracking
2. 直接操控——1:1追踪
"Touch and content should move together."
When the user drags something, it must stay glued to the finger — and respect the offset from where they grabbed it. Snapping to the element's center on grab breaks the illusion immediately.
- Use Pointer Events with so tracking continues even when the pointer leaves the element's bounds.
setPointerCapture - Track a short velocity/position history (last few events), not just the current point — you'll need velocity at release.
pointermove
js
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
});“触摸操作与内容应同步移动。”
当用户拖拽某个元素时,它必须紧贴手指——并且要尊重用户抓取时的偏移量。抓取时自动对齐元素中心会立刻打破真实感。
- 使用Pointer Events搭配,这样即使指针离开元素边界,追踪仍会继续。
setPointerCapture - 记录一段简短的速度/位置历史(最近几次事件),而非仅记录当前点——释放时需要用到速度数据。
pointermove
js
el.addEventListener('pointerdown', (e) => {
el.setPointerCapture(e.pointerId);
const grabOffset = e.clientY - el.getBoundingClientRect().top; // 尊重用户抓取的位置
// ...记录位置+时间戳历史以计算速度
});3. Interruptibility — the single most important principle
3. 可中断性——最重要的原则
"The thought and the gesture happen in parallel."
Every animation must be interruptible and redirectable at any moment. A user must be able to grab a moving element mid-flight and reverse it without waiting for the animation to finish. A closing modal the user grabs again should follow the finger — not finish closing first, then reopen.
- Never lock out input during a transition.
- Always animate from the presentation (current) value, never the target value. On interrupt, read the element's live on-screen transform and start the new animation from there. Starting from the logical/target value causes a visible jump.
- Avoid CSS transitions and for anything gesture-driven — they can't be smoothly grabbed and reversed mid-flight. Springs animate from the current value by default, which is exactly what interruption needs.
@keyframes - When a gesture reverses, blend velocity — don't hard-cut it. Replacing one animation with another at a reversal creates a velocity discontinuity, a "brick wall." Spring libraries that carry velocity through a re-target avoid it. (This is what iOS's additive animations do natively; on the web, choose a spring library that re-targets from the current velocity.)
- Decompose 2D motion into independent X and Y springs. A single spring on a 2D distance desyncs when X and Y have different velocities.
“思考与手势是并行发生的。”
每一个动画都必须能随时被中断和重定向。用户必须能够在元素运动途中抓取并反向操作,无需等待动画完成。用户再次抓取正在关闭的模态框时,它应跟随手指移动——而非先完成关闭再重新打开。
- 过渡期间绝不锁定输入。
- 始终从当前显示值开始动画,而非目标值。中断时,读取元素当前在屏幕上的实时变换值,并从该值开始新的动画。从逻辑/目标值开始会导致可见的跳变。
- 避免对任何手势驱动的操作使用CSS过渡和——它们无法在运动途中被平滑抓取和反向。弹簧动画默认从当前值开始,这正是中断所需的特性。
@keyframes - 当手势反向时,融合速度——不要硬切断。在反向时直接替换动画会造成速度不连续,形成“砖墙”效果。支持在重新定位时延续速度的弹簧库可以避免这种情况。(这正是iOS原生的叠加动画的工作方式;在Web端,选择支持从当前速度重新定位的弹簧库。)
- 将2D运动分解为独立的X轴和Y轴弹簧动画。对2D距离使用单个弹簧会在X轴和Y轴速度不同步时导致错位。
4. Behavior over animation — use springs
4. 行为优先于动画——使用弹簧动画
"Think of animation as a conversation between you and the object, not something prescribed by the interface."
A pre-scripted, fixed-duration animation can't respond to new input. A spring can — new input just changes the target, and the motion stays continuous. Reach for springs for anything a user can touch.
Apple deliberately replaced the physics triplet (mass/stiffness/damping) with two designer-friendly parameters. Think in these:
- Damping ratio — controls overshoot. = critically damped, no bounce, smooth settle.
1.0= overshoots and oscillates. Lower = bouncier.< 1.0 - Response — how quickly the value reaches the target, in seconds. Lower = snappier. This is not "duration" — a spring has no fixed duration; its settle time emerges from the parameters.
Defaults:
- Start most UI at damping (critically damped) — graceful and non-distracting.
1.0 - Add bounce (damping ~) only when the gesture itself carried momentum (a flick, a throw, a drag release). Overshoot on a menu that just faded in feels wrong; overshoot on a card you flicked feels right.
0.8
Concrete values Apple ships:
| Interaction | Damping | Response |
|---|---|---|
| Move / reposition (e.g. PiP) | | |
| Rotation | | |
| Drawer / sheet | | |
Web mapping (Motion / Framer Motion): the + spring API maps closely to Apple's damping + response. A safe house style is springs everywhere by default; reserve bounce for momentum-driven, physical interactions.
bouncedurationdamping: 1.0js
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 });“将动画视为你与对象之间的对话,而非界面预设的内容。”
预脚本化、固定时长的动画无法响应新的输入。而弹簧动画可以——新的输入只需改变目标值,运动就能保持连续。对于任何用户可触摸的元素,优先选择弹簧动画。
Apple特意将物理三要素(质量/刚度/阻尼)替换为两个设计师友好的参数。可以这样理解:
- 阻尼比——控制过冲量。= 临界阻尼,无弹跳,平滑归位。
1.0= 会过冲并振荡。值越小,弹跳感越强。< 1.0 - 响应时间——数值到达目标的速度,单位为秒。值越小,响应越灵敏。这不是“时长”——弹簧动画没有固定时长;它的归位时间由参数决定。
默认值:
- 大多数UI初始设置为阻尼(临界阻尼)——优雅且不分散注意力。
1.0 - 仅当手势本身带有动量时(轻拂、抛掷、拖拽释放),才添加弹跳效果(阻尼≈)。淡入的菜单添加过冲会显得违和;而轻拂卡片时添加过冲则很自然。
0.8
Apple官方推荐的具体值:
| 交互类型 | 阻尼 | 响应时间 |
|---|---|---|
| 移动/重新定位(如画中画) | | |
| 旋转 | | |
| 抽屉/面板 | | |
Web端映射(Motion / Framer Motion): + 弹簧API与Apple的阻尼+响应时间高度匹配。安全的通用风格是默认使用的弹簧动画;仅在动量驱动的物理交互中保留弹跳效果。
bouncedurationdamping: 1.0js
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. Velocity handoff — the seam between drag and animation
5. 速度传递——拖拽与动画的无缝衔接
When a gesture ends, the animation must continue at the finger's exact velocity, so there's no visible seam between dragging and animating. This is the detail that most separates "fluid" from "fine."
Pass the pointer's release velocity as the spring's initial velocity. Some spring APIs want relative velocity — normalize it by the remaining distance to the target:
relativeVelocity = gestureVelocity / (targetValue − currentValue)Example: element at , target (100px to go), finger moving 50px/s → initial spring velocity = . Framer Motion / Motion take absolute px/s velocity directly ( option), so you usually hand it the raw value.
y=50y=15050 / 100 = 0.5velocity当手势结束时,动画必须以手指的精确速度继续,这样拖拽和动画之间就不会出现可见的断层。这是区分“流畅”与“不错”的关键细节。
将指针释放时的速度作为弹簧动画的初始速度。有些弹簧API需要相对速度——用剩余到目标的距离进行归一化:
relativeVelocity = gestureVelocity / (targetValue − currentValue)示例:元素位于,目标为(剩余100px),手指移动速度为50px/s → 弹簧初始速度 = 。Framer Motion / Motion直接接受绝对px/s速度(选项),因此通常直接传入原始值即可。
y=50y=15050 / 100 = 0.5velocity6. Momentum projection — animate to where the gesture is going
6. 动量预测——动画到手势指向的位置
"Take a small input and make a big output."
Don't snap to the nearest boundary from the release point. Use velocity to project the resting position — exactly like scroll deceleration — then snap to the target nearest that projected point. This is what makes a flick feel like it throws the element.
Apple's exact projection function (from the Designing Fluid Interfaces sample code):
js
// 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)Note: the physics-textbook is not what Apple ships — use the exponential-decay form above. This is the standard behavior in good bottom-sheets and carousels (Vaul, Embla).
v²/(2·decel)“将小输入转化为大输出。”
不要从释放点直接吸附到最近的边界。利用速度预测静止位置——就像滚动减速一样——然后吸附到该预测点最近的目标。这就是轻拂动作能产生抛掷感的原因。
Apple的精确预测函数(来自《设计流畅界面》示例代码):
js
// 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节)注意:物理课本中的并非Apple实际使用的公式——请使用上述指数衰减形式。这是优质底部面板和轮播组件(如Vaul、Embla)的标准行为。
v²/(2·decel)7. Spatial consistency — symmetric paths, anchored origins
7. 空间一致性——对称路径、锚定原点
"If something disappears one way, we expect it to emerge from where it came."
- Enter and exit along the same path. A panel that slides in from the right must dismiss to the right. In-from-right / out-the-bottom feels disconnected and confusing.
- Anchor interactions to their source. A menu, popover, or sheet should originate from the element that triggered it — set to the trigger, so the spatial relationship between button and content is obvious. (This is the same origin-awareness point as popovers scaling from their trigger, not their center.)
transform-origin - Mirror the easing on reversible transitions so the outbound path matches the return path (use inverse cubic-bézier control points for the two directions).
“如果元素从某个方向消失,我们期望它从同一方向出现。”
- 沿相同路径进入和退出。从右侧滑入的面板必须向右侧关闭。从右侧进入、从底部退出会显得脱节且令人困惑。
- 将交互锚定到触发源。菜单、弹出框或面板应从触发它的元素位置展开——设置为触发元素,这样按钮与内容之间的空间关系就一目了然。(这与弹出框从触发元素而非中心缩放的原点感知原则一致。)
transform-origin - 可逆过渡效果镜像缓动曲线,使退出路径与返回路径匹配(两个方向使用反向三次贝塞尔控制点)。
8. Hint in the direction of the gesture
8. 手势方向提示
Humans predict a final state from a trajectory. Intermediate motion should telegraph where things are going — Control Center modules "grow up and out toward your finger." Make the in-between frames point at the outcome, not just interpolate blindly to it.
人类会根据轨迹预测最终状态。中间的运动状态应预示元素的去向——控制中心模块会“朝着手指方向向上向外展开”。让过渡帧指向最终结果,而非盲目插值。
9. Rubber-banding — soft boundaries
9. 橡皮筋效果——柔和边界
At an edge, resist progressively instead of stopping hard. A hard stop reads as "frozen"; continuous resistance reads as "responsive, but there's nothing more here." Apply damping that increases the further past the boundary the user drags.
js
// 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));
}在边界处,逐步增加阻力而非突然停止。硬停止会让人感觉“冻结”;连续的阻力则传达“响应正常,但已到达边界”的信号。当用户拖拽超出边界时,应用随偏移量增加而增强的阻尼。
js
// 超出边界越多,元素跟随的幅度越小——真实物体在停止前会减速
function rubberband(overshoot, dimension, constant = 0.55) {
return (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
}10. Gesture design details (the "feel" checklist)
10. 手势设计细节(“手感”检查清单)
- Tap: highlight on touch-down (instant), commit on touch-up. Add ~10px of hysteresis/hit padding around the target, and allow cancel-by-dragging-away and back.
- Drag/swipe: require a small movement threshold (hysteresis, ~10px) before committing to a direction, then track 1:1.
- Detect all plausible gestures in parallel from the first move, then confidently cancel the losers once intent is clear. Avoid recognizers that only report a final state (-type events) — they throw away the continuous tracking you need for feedback.
swipeleft - Minimize disambiguation delays. Double-tap detection unavoidably delays single taps; only pay that cost where double-tap truly exists.
- **点击:**触摸按下时立即高亮,触摸抬起时确认操作。在目标周围添加约10px的容错点击区域,并允许通过拖拽离开来取消操作,拖拽返回则恢复。
- **拖拽/滑动:**需要一个小的移动阈值(约10px)来确认方向,然后进行1:1追踪。
- 从第一次移动开始并行检测所有可能的手势,一旦明确用户意图,就果断取消其他手势识别。避免仅报告最终状态的识别器(如类事件)——它们会丢失反馈所需的连续追踪数据。
swipeleft - 最小化歧义延迟。双击检测不可避免地会延迟单点击;仅在确实需要双击功能的场景下才付出这个成本。
11. Frame-level smoothness
11. 逐帧流畅度
Smoothness is about what's in the frames, not just the frame rate.
- Keep the per-frame positional change below the perception threshold to avoid strobing.
- For very fast motion, a subtle motion blur / stretch encodes speed and reads better than a hard sharp streak.
- is the web's display-synced clock (Apple uses
requestAnimationFrame). Animate only compositor-friendly properties —CADisplayLinkandtransform— and hint withopacitywhere motion is imminent.will-change
流畅度关乎每一帧的内容,而非仅帧率。
- 保持每帧的位置变化低于感知阈值,避免频闪。
- 对于极快的运动,添加细微的运动模糊/拉伸可以体现速度,比生硬的清晰轨迹效果更好。
- 是Web端的显示同步时钟(Apple使用
requestAnimationFrame)。仅对 compositor友好的属性执行动画——CADisplayLink和transform——并在即将发生运动时用opacity提示浏览器。will-change
12. Materials & depth — translucency conveys hierarchy
12. 材质与层次感——半透明传达层级
Apple uses translucent materials as a floating functional layer that brings structure without stealing focus. On the web, approximate with .
backdrop-filter- Build nav/toolbars/sheets as translucent layers (+ a semi-transparent background) with content scrolling underneath — not opaque bars that consume a fixed strip.
backdrop-filter: blur() - Material weight encodes hierarchy: darker/heavier materials separate structural regions (sidebars); lighter materials draw attention to interactive elements (buttons). Never stack a light translucent surface on another — legibility collapses.
- Bigger surfaces should read as thicker: stronger blur + a deeper shadow than small chips. Consider context-aware shadow — heavier over busy/text content for separation, lighter over plain backgrounds.
- Dim to focus, separate to keep flow. A modal task pairs the surface with a dimming scrim and pushes the background back/down. A parallel, non-blocking panel uses translucency and offset without a scrim so the flow isn't broken. For stacked sheets, progressively dim and push back each parent layer.
- Vibrancy keeps text legible over changing backgrounds. Over blurred/translucent surfaces, don't use flat gray text — use higher-contrast, slightly heavier weight, and a small letter-spacing bump. Put color on a solid layer, not the translucent foreground.
- Scroll edge effects, not hard dividers. Instead of a 1px border under a sticky header, fade a small blur/gradient mask where content meets floating chrome — only where floating UI actually overlaps content.
- Materialize, don't just fade. For glass/blur surfaces, animate blur radius and scale together on enter/exit, so the surface reads as a real material arriving rather than a plain opacity fade.
css
.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 */
}Apple使用半透明材质作为浮动功能层,既能构建结构又不会分散注意力。在Web端,可以用近似实现。
backdrop-filter- 将导航栏/工具栏/面板设计为半透明层(+ 半透明背景),让内容在下方滚动——而非使用占据固定区域的不透明栏。
backdrop-filter: blur() - **材质重量传达层级:**更深/更厚重的材质分隔结构区域(侧边栏);更浅的材质突出交互元素(按钮)。绝不要在另一个浅色半透明表面上叠加浅色半透明表面——可读性会严重下降。
- **更大的表面应显得更厚重:**比小芯片使用更强的模糊和更深的阴影。考虑上下文感知阴影——在繁忙/文本内容上方使用更重的阴影以分隔,在纯色背景上方使用更轻的阴影。
- 调暗以聚焦,分隔以保持流程。模态任务搭配表面调暗幕布,并将背景向后/向下推。并行的非阻塞面板使用半透明和偏移,不添加幕布,这样流程不会被打断。对于堆叠面板,逐步调暗并向后推每个父层。
- 活力效果确保文本在动态背景上可读。在模糊/半透明表面上,不要使用纯灰色文本——使用更高对比度、稍粗的字重和微小的字距增加。将颜色放在纯色层上,而非半透明前景。
- 滚动边缘效果,而非硬分隔线。不要在粘性标题下方使用1px边框,而是在内容与浮动界面重叠处添加小的模糊/渐变遮罩——仅在浮动UI实际覆盖内容的位置添加。
- 具象化,而非仅淡入淡出。对于玻璃/模糊表面,在进入/退出时同步动画模糊半径和缩放,让表面看起来是真实材质的到来,而非单纯的透明度淡入淡出。
css
.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. Multimodal feedback — motion + sound + haptics
13. 多模态反馈——动效+声音+触觉
Three rules for combining senses (from Designing Audio-Haptic Experiences):
- Causality — it must be obvious what caused the feedback. Trigger it on the actual causal event (the toggle flipping, the item snapping home), and match its character to the action's physicality.
- Harmony — the visual, the sound, and the haptic must fire on the same frame. Latency between them destroys the illusion. Don't let a CSS transition lag the audio/haptic (Vibration API).
- Utility — add feedback only where it earns its place. Reserve haptics/sound for meaningful moments (success, error, commit, snap). Over-feedback trains users to ignore all of it.
结合多感官的三条规则(来自《设计音频-触觉体验》):
- 因果性——必须明确是什么触发了反馈。在实际的因果事件(开关切换、元素归位)时触发,并使其特性与动作的物理属性匹配。
- 协调性——视觉、声音和触觉必须在同一帧触发。它们之间的延迟会打破真实感。不要让CSS过渡滞后于音频/触觉反馈(Vibration API)。
- 实用性——仅在反馈能发挥作用时添加。将触觉/声音反馈留给有意义的时刻(成功、错误、确认、归位)。过度反馈会让用户忽略所有提示。
14. Reduced motion & accessibility
14. 减少动效与无障碍
Reduced motion doesn't mean no feedback — it means a gentler, non-vestibular equivalent. Respond to three independent signals and bake them into your components:
- — replace slides/springs/parallax with short opacity cross-fades or static transitions. Drop elastic/overshoot. Keep opacity/color changes that aid comprehension.
prefers-reduced-motion: reduce - — make translucent surfaces frostier/solid: raise background opacity, drop the blur.
prefers-reduced-transparency: reduce - — near-solid backgrounds with a defined, contrasting border.
prefers-contrast: more
Also: avoid full-viewport moving backgrounds, slow looping oscillations (near 0.2 Hz / one cycle per 5s), and abrupt brightness jumps (ease dark↔light theme changes). Make large moving objects semi-transparent while they travel, and fade big surfaces out during a large reposition and back in once settled.
css
@media (prefers-reduced-motion: reduce) {
.sheet { transition: opacity 200ms ease; transform: none !important; }
}
@media (prefers-reduced-transparency: reduce) {
.toolbar { background: white; backdrop-filter: none; }
}减少动效并不意味着完全取消反馈——而是使用更温和、不会引发前庭不适的替代方案。响应三个独立的信号并将其融入组件:
- ——用短时长透明度交叉淡入淡出或静态过渡替代滑动/弹簧/视差效果。移除弹性/过冲效果。保留有助于理解的透明度/颜色变化。
prefers-reduced-motion: reduce - ——让半透明表面更模糊/更实:提高背景透明度,取消模糊效果。
prefers-reduced-transparency: reduce - ——使用近乎纯色的背景和清晰的对比边框。
prefers-contrast: more
此外:避免全屏移动背景、缓慢循环振荡(约0.2Hz / 每5秒一个周期)和突然的亮度跳变(平滑切换明暗主题)。大型移动对象在移动时设置为半透明,在大幅重新定位时淡出,归位后再淡入。
css
@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. Typography — optical sizing, tracking, leading
15. 排版——光学尺寸、字距、行高
Apple designs type to change shape with size; the same discipline applies on the web. (From The Details of UI Typography, WWDC 2020.)
- Tracking (letter-spacing) is size-specific — never one value for all sizes. Large display text wants negative tracking (letters read too far apart as they grow); small text wants slightly positive tracking for legibility. A fixed is wrong somewhere. Tighten headings, leave body near
letter-spacing.0 - Leading (line-height) tracks size inversely. Tight on large headings, looser on body copy. Increase it for scripts with tall ascenders/descenders; tighten it for dense, information-heavy UI.
- Build hierarchy from weight + size + leading as a set, not size alone. Emphasize with weight — it adds presence without taking more space.
- Respect the user's text-size setting (Dynamic Type). Scale layout with the text — spacing in /
rem, not fixed px — so a larger font doesn't break the layout.em - Default to the platform's system font before a custom face; it already ships optical sizing, tracking tables, and legibility tuning. Override only with a reason.
css
: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;
}Apple设计的字体会随尺寸变化形态;这一原则同样适用于Web端。(来自《UI排版细节》,WWDC 2020。)
- 字距(letter-spacing)随尺寸变化——绝不为所有尺寸设置单一值。大标题文本需要负字距(文本变大时字母间距会显得过宽);小文本需要轻微正字距以提高可读性。固定的总会在某些场景下出错。收紧标题字距,正文字距保持接近
letter-spacing。0 - 行高(line-height)与尺寸成反比。大标题行高较紧凑,正文字距较宽松。对于带有高升部/降部的脚本,增加行高;对于密集的信息型UI,收紧行高。
- 通过字重+尺寸+行高的组合构建层级,而非仅依赖尺寸。用字重强调——它能增加存在感而不占用更多空间。
- 尊重用户的文本大小设置(动态字体)。随文本缩放布局——使用/
rem设置间距,而非固定px——这样更大的字体不会破坏布局。em - 优先使用平台系统字体,再考虑自定义字体;系统字体已内置光学尺寸、字距表和可读性优化。仅在有充分理由时才替换。
css
: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. Design foundations — the eight principles
16. 设计基础——八大原则
The motion and craft above serve Apple's eight design principles (Principles of Great Design, WWDC 2026). Use these as the names you reason with:
- Purpose. Make with intention; decide what not to build. Every feature asks for the user's time, attention, and trust — spend that budget only where it pays off.
- Agency. Keep people in control: offer choices, don't force a single path. Back it with forgiveness — easy undo for slips, a confirmation dialog only for genuinely destructive, irreversible actions (use sparingly; overusing it trains people to click through).
- Responsibility. Act in the user's interest. Privacy: ask at the right moment, only for what's needed, transparently. Safety: anticipate misuse and harm — especially with AI (an allergy-aware recipe app must not suggest a harmful ingredient). Add previews, confirmations, disclaimers; cut a feature whose risk outweighs its value.
- Familiarity. Build on what people already know. Use metaphors that are neither too literal nor too abstract (a trash can means delete), and honor their physics. Be consistent: things that look the same must behave the same and live in the same place (close is always top-left on macOS) so people can predict what happens next. Only break a familiar pattern if you can prove it's better — then test it, don't assume.
- Flexibility. Design for different contexts, devices, and the full range of abilities. Adapt to the platform (iPhone = quick touch; desktop = deep workflows with precise pointer control) and to the situation. Design inclusively (age, language, expertise, accessibility). When no single layout fits everyone, let people personalize — rearrange controls, hide what they don't use.
- Simplicity — not minimalism. Strip the unnecessary so the core purpose shines; burying everything in one place looks minimal but isn't simple. Be concise (plain language, no jargon, fewer steps) and clear (use hierarchy — order, spacing, contrast — so the most important thing is the most obvious). Every element earns its place; sometimes adding context simplifies (a video scrubber that shows time remaining). Show the common path first, advanced options one level deeper.
- Craft. Uncompromising attention to detail builds trust. Beautiful typography, colors that adapt to light/dark, clear iconography, and responsive animations that give immediate, natural feedback. Nothing is random — every spacing, timing, and alignment value is a deliberate choice you can defend. Jittery scroll, misaligned icons, and layouts that break on rotation read as carelessness. Craft needs iteration and longevity — keep evolving the design as features and hardware change.
- Delight. The result of getting the other seven right, not confetti tacked on top. Decide the emotion you want people to feel (calm, confident, excited) and reinforce it in every decision.
Tactical rules that serve these:
- Feedback comes in four kinds: status, completion, warning, error. Confirm meaningful actions, expose ongoing status, warn before problems, validate inline (not on submit).
- Wayfinding. Every screen should answer: Where am I? Where can I go? What's there? How do I get out? Never trap the user.
- Grouping & mapping. Proximity implies relationship; place a control near what it affects and arrange controls to mirror what they change. If you need a label to explain a control, the mapping is weak.
- Direct, specific labels beat safe generic ones. Name nav items for their contents ("Progress", "Library"), not vague umbrellas ("Home"). Specificity creates predictability.
上述动效与设计细节都服务于Apple的八大设计原则(《优秀设计原则》,WWDC 2026)。可以用这些原则作为思考框架:
- 目的性。带着意图设计;决定哪些内容不需要构建。每个功能都会占用用户的时间、注意力和信任——仅在能带来回报的场景下使用这些资源。
- 自主性。让用户保持掌控:提供选择,不要强制单一路径。辅以容错机制——失误时易于撤销,仅对真正具有破坏性、不可逆转的操作使用确认对话框(谨慎使用;过度使用会让用户习惯性点击确认)。
- 责任感。为用户利益着想。隐私:在合适的时机请求权限,仅索取必要信息,保持透明。安全:预判误用和危害——尤其是AI应用(一款过敏友好的食谱应用绝不能推荐有害食材)。添加预览、确认、免责声明;砍掉风险大于价值的功能。
- 熟悉性。基于用户已有的认知进行设计。使用既不过于直白也不过于抽象的隐喻(垃圾桶代表删除),并遵循物理规律。保持一致性:外观相同的元素必须行为相同且位置一致(macOS上关闭按钮始终在左上角),这样用户可以预测后续操作。仅在能证明新模式更优时才打破熟悉的模式——然后进行测试,不要想当然。
- 灵活性。为不同场景、设备和全能力范围的用户设计。适配平台(iPhone=快速触摸;桌面=带精确指针控制的深度工作流)和使用情境。包容性设计(考虑年龄、语言、专业能力、无障碍需求)。当单一布局无法满足所有人时,允许用户个性化——重新排列控件,隐藏不需要的功能。
- 简洁性——而非极简主义。剔除不必要的内容,让核心目的凸显;将所有内容隐藏在一处看起来极简,但并不简洁。保持简洁(使用平实语言,无行话,减少步骤)和清晰(使用层级——顺序、间距、对比度——让最重要的内容最显眼)。每个元素都要有存在的理由;有时添加上下文反而会简化(显示剩余时间的视频进度条)。先展示常用路径,高级选项放在下一层。
- 匠心。对细节的极致关注能建立信任。优美的排版、适配明暗的色彩、清晰的图标,以及能提供即时自然反馈的响应式动画。没有任何内容是随机的——每一个间距、时长和对齐值都是你能为之辩护的深思熟虑的选择。抖动的滚动、错位的图标、旋转时崩溃的布局会显得粗心。匠心需要迭代和长期维护——随着功能和硬件的变化不断优化设计。
- 愉悦感。这是做好其他七条原则的结果,而非事后添加的点缀。决定你希望用户感受到的情绪(平静、自信、兴奋),并在每一个决策中强化这种情绪。
服务于这些原则的实用规则:
- **反馈分为四类:**状态、完成、警告、错误。确认有意义的操作,展示进行中的状态,在问题出现前发出警告,实时验证(而非提交时)。
- 导航指引。每个屏幕都应回答:我在哪里?我可以去哪里?那里有什么?我如何退出?永远不要困住用户。
- 分组与映射。 proximity意味着关联;将控件放在它影响的内容附近,并按控件所改变的内容排列布局。如果需要标签来解释控件,说明映射关系不够直观。
- 直接具体的标签优于安全通用的标签。导航项以内容命名(“进度”、“库”),而非模糊的统称(“首页”)。具体性能提升可预测性。
17. Process
17. 流程
- Prototype interactively — an interactive demo is worth "a million static designs." You discover the interface by building and playing with it; a working prototype also sets a concrete bar that prevents a mediocre final implementation.
- Design interaction and visuals together. "You shouldn't be able to tell where one ends and the other begins." Motion is not a layer added after the pixels.
- Test with real people in real context, and review motion with fresh eyes — play it in slow motion / frame-by-frame to catch what's invisible at full speed.
- 交互式原型——一个交互式演示抵得上“一万个静态设计”。你通过构建和试用发现界面的问题;可运行的原型还能设定明确的标准,避免最终实现质量平庸。
- 交互与视觉设计同步进行。“你应该无法区分两者的界限。”动效不是在像素完成后添加的层。
- 在真实场景中测试真实用户,并用新鲜视角评审动效——慢动作/逐帧播放以捕捉全速时不可见的细节。
Quick Reference
快速参考
| Need | Technique | Concrete value |
|---|---|---|
| Default UI spring | Critically damped, no overshoot | |
| Momentum / flick spring | Under-damped, slight bounce | |
| Gesture → spring velocity | Hand off release velocity | |
| Flick landing point | Project momentum | |
| Interrupt cleanly | Start from presentation (live) value | read the on-screen transform |
| Avoid reversal "brick wall" | Carry velocity through re-target | spring that blends velocity |
| Reversible transition | Mirror the easing curve | inverse cubic-bézier |
| Decide reverse vs. commit | Use velocity sign, not position | at release |
| 1:1 drag | Pointer Events + capture | respect the grab offset |
| Feedback | On pointer-down, continuous | never only at the end |
| Boundary | Rubber-band, don't hard-stop | progressive resistance |
| Translucent chrome | | content scrolls under |
| Type tracking | Size-specific, never fixed | tighten large text ( |
| Reduced motion | Cross-fade, not slide/spring | |
| 需求 | 技巧 | 具体值 |
|---|---|---|
| 默认UI弹簧动画 | 临界阻尼,无过冲 | |
| 动量/轻拂弹簧动画 | 欠阻尼,轻微弹跳 | |
| 手势→弹簧速度传递 | 传递释放时的速度 | 若需归一化则使用 |
| 轻拂落点预测 | 动量预测 | |
| 平滑中断 | 从当前显示值开始 | 读取屏幕上的实时变换值 |
| 避免反向“砖墙”效果 | 重定位时延续速度 | 支持融合速度的弹簧动画 |
| 可逆过渡效果 | 镜像缓动曲线 | 反向三次贝塞尔曲线 |
| 判断反向还是确认 | 使用速度符号,而非位置 | 在释放时判断 |
| 1:1拖拽 | Pointer Events + 捕获 | 尊重抓取偏移量 |
| 反馈 | 指针按下时触发,持续提供 | 绝不仅在结束时提供 |
| 边界处理 | 橡皮筋效果,而非硬停止 | 渐进式阻力 |
| 半透明界面 | | 内容在下方滚动 |
| 排版字距 | 随尺寸变化,非固定值 | 大文本收紧( |
| 减少动效 | 交叉淡入淡出,而非滑动/弹簧 | |