Communitygithub.com

RevanthThota55/FreeFrame-Studio

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

FreeFrame-Studio 是什么?

该技能将苹果WWDC设计讲座(尤其是2018年《设计流畅界面》中的核心理念提炼并转化为Web平台(CSS、指针事件、requestAnimationFrame、Motion/Framer Motion等弹簧动画库)的实践指南。它强调流畅界面的本质在于:动画从屏幕上的当前值开始,继承用户的速度,向前投射动量,并可在任何瞬间被抓住和逆转。弹簧动画是这一切的核心工具,因为它们天然可中断且感知速度。技能覆盖了从即时响应、直接操作、可中断性、弹簧行为、速度交接、动量投影到空间一致性、橡皮筋效果、手势设计细节、帧级平滑、材质与深度以及多模态反馈等十三个关键主题,为开发者提供了一套完整的、可落地的苹果风格界面设计方法论。它适用于任何需要构建自然、物理化、可中断的Web交互场景。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/RevanthThota55/FreeFrame-Studio/tree/HEAD/.claude/skills/apple-design

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Apple Design

初始响应

当首次调用此技能且没有具体问题时,仅回复:

我已准备好帮助您构建流畅的、苹果风格的Web界面。我的知识源自苹果WWDC设计讲座,已转化为Web实践。

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

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

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

核心理念

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

一个流畅的界面,其行为就像物理世界:事物瞬间响应,连续移动,携带动量,在边界处产生阻力,并且可以在运动中途被重新定向。以下所有内容都是为了让界面更接近这种状态。

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

1. 响应——消除延迟

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

  • 在手指按下时响应,而不是在松手时。 按钮被按下的瞬间就应给出视觉反馈。等待 click/触摸抬起事件才显示反馈会显得死气沉沉。
  • 警惕每一次延迟。 检查防抖、人为计时器、过渡等待时间以及大约 300ms 的点击延迟。任何输入路径上非必要的延迟都是倒退。
  • 交互过程中反馈必须持续 存在,而不仅仅在结束时。 对于拖拽、滑块或抽屉,在指针移动的全过程中,UI 必须与指针 1:1 同步更新——绝不要只在手势完成时才播放动画。
/* 反馈存在于按下瞬间,且需即时触发 */
.button:active {
  transform: scale(0.97);
  transition: transform 100ms ease-out;
}

2. 直接操作——1:1 追踪

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

当用户拖拽某个元素时,它必须像粘在手指上一样移动——并且要尊重 抓取时的偏移量。在抓取时瞬间将元素吸附到中心会立刻打破沉浸感。

  • 使用带 setPointerCapture 的指针事件,这样即使指针移出元素边界,追踪也能持续。
  • 记录一个简短的速度/位置历史(最近几次 pointermove 事件),而不仅仅是当前点——你需要在释放时用到速度信息。
el.addEventListener('pointerdown', (e) => {
  el.setPointerCapture(e.pointerId);
  const grabOffset = e.clientY - el.getBoundingClientRect().top; // 尊重抓取位置
  // ...记录位置+时间戳历史以计算速度
});

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

“想法和手势是同时发生的。”

每一个动画都必须是可中断的,并且可以在任何时刻被重定向。用户必须能够在半空中抓住一个正在移动的元素并反向拖动它,而无需等待动画完成。一个用户再次抓取的关闭中的弹窗,应该跟随手指移动——而不是先完成关闭动作,再重新打开。

  • 绝不在过渡期间锁定输入。
  • 始终从 当前显示(实时)值开始动画,而不是目标值。 中断时,读取元素在屏幕上的实时变换属性,并从这个值开始新动画。从逻辑/目标值开始会导致可见的跳跃。
  • 对于手势驱动的交互,避免使用 CSS 过渡和 @keyframes——它们无法在半空中被平滑地抓取和逆转。弹簧动画默认从当前值开始动画,这正好满足了可中断性的需求。
  • 当手势反转时,融合速度——不要硬切。 在反转点用新动画替换旧动画会造成速度的不连续性,如同撞上一堵“砖墙”。那些能在重新指定目标时携带速度的弹簧库可以避免这个问题。(这是 iOS 原生 叠加动画 的做法;在 Web 上,选择一个能在重设目标时继承当前速度的弹簧库。)
  • 将 2D 运动分解为独立的 X 和 Y 弹簧。 在一个 2D 距离上使用单弹簧,会在 X 和 Y 方向速度不同时导致不同步。

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

“把动画看作你和对象之间的对话,而不是界面预设的脚本。”

一个预设的、固定时长的动画无法响应新的输入。而弹簧可以——新的输入只需改变目标值,运动就能保持连续。对于任何用户可以触摸的交互,都应优先考虑弹簧。

苹果有意将物理三件套(质量/刚度/阻尼)替换为两个对设计师友好的参数。请这样理解它们:

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

默认值:

  • 大多数 UI 交互从 阻尼 1.0(临界阻尼)开始——优雅且不分散注意力。
  • 只有当手势本身携带了动量时(如轻扫、投掷、拖拽释放),才添加弹跳(阻尼约 0.8)。一个刚淡入的菜单出现弹跳会感觉很奇怪;但你抛出的卡片有过冲则感觉自然。

苹果在制品中使用的具体值:

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

Web 映射(Motion / Framer Motion): bounce + duration 弹簧 API 与苹果的阻尼 + 响应模型高度对应。一个安全的基础风格是默认在所有场景使用 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. 动量投射——动画去向手势正指向的位置

“以小输入产生大输出。”

不要从释放点直接吸附到最近的边界。使用速度来投射静止位置——就像滚动减速一样——然后吸附到最接近该投射点的目标。这就是让轻扫感觉像抛掷元素的关键。

苹果的精确投射函数(来自《设计流畅界面》示例代码):

// 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) 不是 苹果实际采用的方式——请使用上述的指数衰减形式。这是优质底部面板和轮播库(如 Vaul、Embla)的标准行为。

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

“如果某物以一种方式消失,我们期望它从其来源处出现。”

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

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 上与显示同步的时钟(苹果使用 CADisplayLink)。仅动画化那些对合成器友好的属性—— transform 和 opacity——并在即将产生运动的地方提示使用 will-change。

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

苹果使用半透明材质作为浮动的功能层,在构建结构的同时不抢夺注意力。在 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. 和谐——视觉、声音和触觉必须在同一帧内触发。它们之间的延迟会破坏沉浸感。不要让一个动画……

…[内容因篇幅限制而截断]

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

RevanthThota55/FreeFrame-Studio

Build an animation from scratch, making the decisions in the order that determines whether it feels right — should it animate at all, what purpose, which tool, which properties, which curve and duration, how it interrupts, how it exits. Writes the implementation. Use when asked to animate something, add motion, make a component feel alive, or build a transition. For critiquing existing motion use review-animations; for auditing a whole codebase use improve-animations.

RevanthThota55/FreeFrame-Studio

Keep web animations inside the frame budget — animate composite-only properties, keep motion off the main thread when the page is busy, and find the cause when an animation drops frames. Covers the layout/paint/composite pipeline, cheaper property swaps, CSS vs WAAPI vs requestAnimationFrame, Framer Motion's hardware-accelerated path, React re-rendering per frame, inherited CSS variable recalculation, blur cost, and when `will-change` actually helps. Use when an animation is janky, choppy, stutters, or drops frames; when choosing which property to animate; when deciding between CSS and JS animation on performance grounds; or when reaching for `will-change` or GPU layers. For choosing easing, duration, or whether to animate at all use animate; for reviewing motion as a whole use review-animations.

RevanthThota55/FreeFrame-Studio

Reverse-lookup glossary that turns a vague description of a web animation or motion effect into its exact term ("the bouncy thing when a popover opens" → Pop in; "the iOS rubber-band scroll" → Rubber-banding). Use when the user asks "what's it called when…", or describes a motion effect without knowing its name and wants the right word to prompt an AI or designer with. For naming an effect, not designing or building one.

相关技能